|
|
发表于 2026-1-31 10:56:26
|
显示全部楼层
完全基于 Linux 系统的原生 socket 实现,不使用三方库的实现,包含 “连接网关→读寄存器(获取报文)→写寄存器(发送速度数据)” 全流程,注释详细,可直接编译运行:
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #include <sys/socket.h>
- #include <arpa/inet.h>
- #include <errno.h>
- // ===================== Modbus TCP 协议常量 =====================
- #define MODBUS_TCP_PORT 502 // Modbus TCP默认端口
- #define MODBUS_MBAP_HEADER_LEN 6 // MBAP头长度(字节)
- #define MODBUS_READ_HOLDING_REG_FUNC 0x03 // 读保持寄存器功能码
- #define MODBUS_WRITE_SINGLE_REG_FUNC 0x06 // 写单个寄存器功能码
- // ===================== 网关/设备配置(根据实际修改) =====================
- #define GATEWAY_IP "192.168.1.100" // 网关IP
- #define SLAVE_ID 0x01 // 从站地址(网关/RS485设备)
- #define SPEED_REG_ADDR 40001 // 速度寄存器地址(保持寄存器)
- #define REG_NUM 1 // 读写寄存器数量
- // 函数:创建并连接TCP套接字
- int create_tcp_socket(const char* ip, int port) {
- // 1. 创建TCP套接字
- int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
- if (sock_fd < 0) {
- fprintf(stderr, "创建socket失败:%s\n", strerror(errno));
- return -1;
- }
- // 2. 配置网关地址结构
- struct sockaddr_in server_addr;
- memset(&server_addr, 0, sizeof(server_addr));
- server_addr.sin_family = AF_INET;
- server_addr.sin_port = htons(port); // 转换为网络字节序(大端)
-
- // 转换IP字符串为网络字节序
- if (inet_pton(AF_INET, ip, &server_addr.sin_addr) <= 0) {
- fprintf(stderr, "IP地址无效:%s\n", ip);
- close(sock_fd);
- return -1;
- }
- // 3. 连接网关
- if (connect(sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
- fprintf(stderr, "连接网关失败:%s\n", strerror(errno));
- close(sock_fd);
- return -1;
- }
- printf("成功连接到网关 %s:%d\n", ip, port);
- return sock_fd;
- }
- // 函数:构造Modbus TCP读保持寄存器报文
- // 返回值:构造的报文长度
- int build_read_reg_packet(uint8_t* packet, uint16_t trans_id, uint16_t reg_addr, uint16_t reg_num) {
- // 步骤1:转换寄存器地址(Modbus 40001 = 0x0000,需减40001)
- uint16_t modbus_addr = reg_addr - 40001;
- // 步骤2:构造MBAP头(6字节)
- packet[0] = (trans_id >> 8) & 0xFF; // 事务ID高字节
- packet[1] = trans_id & 0xFF; // 事务ID低字节
- packet[2] = 0x00; // 协议ID高字节(固定0)
- packet[3] = 0x00; // 协议ID低字节(固定0)
- packet[4] = (0x06 >> 8) & 0xFF; // 后续长度高字节(PDU长度=6)
- packet[5] = 0x06 & 0xFF; // 后续长度低字节
- // 步骤3:构造PDU(协议数据单元,6字节)
- packet[6] = SLAVE_ID; // 从站地址
- packet[7] = MODBUS_READ_HOLDING_REG_FUNC; // 功能码(0x03)
- packet[8] = (modbus_addr >> 8) & 0xFF; // 寄存器地址高字节
- packet[9] = modbus_addr & 0xFF; // 寄存器地址低字节
- packet[10] = (reg_num >> 8) & 0xFF; // 寄存器数量高字节
- packet[11] = reg_num & 0xFF; // 寄存器数量低字节
- return MODBUS_MBAP_HEADER_LEN + 6; // 总报文长度:6+6=12字节
- }
- // 函数:构造Modbus TCP写单个寄存器报文
- int build_write_reg_packet(uint8_t* packet, uint16_t trans_id, uint16_t reg_addr, uint16_t value) {
- // 转换寄存器地址
- uint16_t modbus_addr = reg_addr - 40001;
- // MBAP头(6字节)
- packet[0] = (trans_id >> 8) & 0xFF; // 事务ID高字节
- packet[1] = trans_id & 0xFF; // 事务ID低字节
- packet[2] = 0x00; // 协议ID(固定0)
- packet[3] = 0x00;
- packet[4] = (0x06 >> 8) & 0xFF; // PDU长度=6
- packet[5] = 0x06 & 0xFF;
- // PDU(6字节)
- packet[6] = SLAVE_ID; // 从站地址
- packet[7] = MODBUS_WRITE_SINGLE_REG_FUNC; // 功能码(0x06)
- packet[8] = (modbus_addr >> 8) & 0xFF; // 寄存器地址高字节
- packet[9] = modbus_addr & 0xFF; // 寄存器地址低字节
- packet[10] = (value >> 8) & 0xFF; // 写入值高字节
- packet[11] = value & 0xFF; // 写入值低字节
- return MODBUS_MBAP_HEADER_LEN + 6; // 总长度12字节
- }
- // 函数:解析Modbus TCP读指令返回的报文
- // 返回值:解析出的寄存器值,失败返回-1
- int parse_read_response(uint8_t* response, int resp_len) {
- // 校验响应长度(至少:MBAP头6 + 从站地址1 + 功能码1 + 字节数1 + 数据2 = 11字节)
- if (resp_len < 11) {
- fprintf(stderr, "响应报文长度异常:%d\n", resp_len);
- return -1;
- }
- // 校验功能码(确认是读保持寄存器响应)
- if (response[7] != MODBUS_READ_HOLDING_REG_FUNC) {
- fprintf(stderr, "功能码不匹配:0x%02X\n", response[7]);
- return -1;
- }
- // 提取寄存器数据(大端序转主机序)
- uint16_t reg_value = (response[9] << 8) | response[10];
- printf("解析网关返回报文成功:寄存器值 = %d\n", reg_value);
- return reg_value;
- }
- // 函数:发送Modbus报文并接收响应
- int send_and_receive(int sock_fd, uint8_t* send_packet, int send_len, uint8_t* recv_buf, int recv_buf_len) {
- // 1. 发送报文到网关
- int send_bytes = write(sock_fd, send_packet, send_len);
- if (send_bytes < 0) {
- fprintf(stderr, "发送报文失败:%s\n", strerror(errno));
- return -1;
- }
- printf("发送Modbus报文成功,长度:%d字节\n", send_bytes);
- // 2. 接收网关响应(阻塞读取)
- int recv_bytes = read(sock_fd, recv_buf, recv_buf_len);
- if (recv_bytes < 0) {
- fprintf(stderr, "接收响应失败:%s\n", strerror(errno));
- return -1;
- }
- printf("接收网关响应报文,长度:%d字节\n", recv_bytes);
- return recv_bytes;
- }
- int main() {
- int sock_fd = -1;
- uint8_t send_packet[256] = {0}; // 发送报文缓冲区
- uint8_t recv_buf[256] = {0}; // 接收响应缓冲区
- uint16_t trans_id = 0x0001; // Modbus事务ID(自增,避免冲突)
- int speed_value = 1500; // 要发送的速度数据
- // 1. 创建TCP连接
- sock_fd = create_tcp_socket(GATEWAY_IP, MODBUS_TCP_PORT);
- if (sock_fd < 0) {
- return -1;
- }
- // 2. 构造并发送读寄存器报文(获取网关报文)
- int send_len = build_read_reg_packet(send_packet, trans_id, SPEED_REG_ADDR, REG_NUM);
- int recv_len = send_and_receive(sock_fd, send_packet, send_len, recv_buf, sizeof(recv_buf));
- if (recv_len > 0) {
- // 解析返回的报文,获取当前寄存器值
- parse_read_response(recv_buf, recv_len);
- }
- // 3. 构造并发送写寄存器报文(发送速度数据)
- trans_id++; // 事务ID自增
- memset(send_packet, 0, sizeof(send_packet));
- send_len = build_write_reg_packet(send_packet, trans_id, SPEED_REG_ADDR, speed_value);
- recv_len = send_and_receive(sock_fd, send_packet, send_len, recv_buf, sizeof(recv_buf));
- if (recv_len > 0) {
- printf("速度数据 %d 已发送到网关\n", speed_value);
- }
- // 4. 关闭套接字
- close(sock_fd);
- return 0;
- }
复制代码 |
|