C语言实现Modbus TCP/IP协议客户端-服务器
C语言实现Modbus TCP/IP协议客户端-服务器,包含完整的报文解析和CRC校验模块:
一、服务器端代码 (modbus_server.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>#define SERVER_PORT 502
#define BUFFER_SIZE 1024// Modbus TCP寄存器模拟数据
uint16_t holding_registers[100] = {0};// CRC16校验函数
uint16_t modbus_crc(uint8_t *buf, int len) {uint16_t crc = 0xFFFF;for(int i=0; i<len; i++) {crc ^= (uint16_t)buf[i];for(int j=0; j<8; j++) {crc >>= 1;if(crc & 0x0001) crc ^= 0xA001;}}return crc;
}// 处理Modbus请求
void handle_modbus_request(uint8_t *buffer, int bytes_received, int client_socket) {// 解析MBAP头uint16_t transaction_id = (buffer[0]<<8) | buffer[1];uint16_t protocol_id = (buffer[2]<<8) | buffer[3];uint16_t length = (buffer[4]<<8) | buffer[5];uint8_t unit_id = buffer[6];uint8_t function_code = buffer[7];// 构建响应头uint8_t response[256] = {0};response[0] = buffer[0]; // 事务ID高字节response[1] = buffer[1]; // 事务ID低字节response[2] = buffer[2]; // 协议ID高字节response[3] = buffer[3]; // 协议ID低字节response[4] = (uint8_t)(length >> 8); // 长度高字节response[5] = (uint8_t)(length & 0xFF); // 长度低字节response[6] = unit_id; // 单元IDresponse[7] = function_code;// 功能码// 处理读保持寄存器请求 (功能码03)if(function_code == 0x03) {uint16_t start_addr = (buffer[8]<<8) | buffer[9];uint16_t reg_count = (buffer[10]<<8) | buffer[11];// 检查地址有效性if(start_addr >= 100 || reg_count == 0) {response[7] |= 0x80; // 设置异常标志response[8] = 0x02; // 非法数据地址send(client_socket, response, 9, 0);return;}// 构建数据域response[8] = (uint8_t)(reg_count * 2); // 字节计数for(int i=0; i<reg_count; i++) {response[9 + i*2] = (uint8_t)(holding_registers[start_addr + i] >> 8);response[10 + i*2] = (uint8_t)(holding_registers[start_addr + i] & 0xFF);}send(client_socket, response, 9 + reg_count*2, 0);}
}int main() {int server_fd, new_socket;struct sockaddr_in address;int addrlen = sizeof(address);// 创建socketif ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("socket failed");exit(EXIT_FAILURE);}// 绑定端口address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(SERVER_PORT);if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("bind failed");exit(EXIT_FAILURE);}// 监听连接if (listen(server_fd, 5) < 0) {perror("listen");exit(EXIT_FAILURE);}printf("Modbus TCP Server listening on port %d\n", SERVER_PORT);while(1) {if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {perror("accept");exit(EXIT_FAILURE);}// 接收请求uint8_t buffer[BUFFER_SIZE] = {0};int bytes_received = recv(new_socket, buffer, BUFFER_SIZE, 0);if(bytes_received > 0) {handle_modbus_request(buffer, bytes_received, new_socket);}close(new_socket);}return 0;
}
二、客户端代码 (modbus_client.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 502
#define BUFFER_SIZE 1024// 构建Modbus请求帧
void build_modbus_request(uint8_t *buffer, uint16_t start_addr, uint16_t reg_count, uint8_t function_code) {// MBAP头buffer[0] = 0x00; // 事务ID高字节buffer[1] = 0x01; // 事务ID低字节buffer[2] = 0x00; // 协议ID高字节buffer[3] = 0x00; // 协议ID低字节buffer[4] = 0x00; // 长度高字节buffer[5] = 0x06; // 长度低字节 (6字节数据)buffer[6] = 0x01; // 单元IDbuffer[7] = function_code; // 功能码// 数据域buffer[8] = (start_addr >> 8) & 0xFF; // 起始地址高字节buffer[9] = start_addr & 0xFF; // 起始地址低字节buffer[10] = (reg_count >> 8) & 0xFF; // 寄存器数量高字节buffer[11] = reg_count & 0xFF; // 寄存器数量低字节
}int main() {int sock = 0;struct sockaddr_in serv_addr;if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {printf("\n Socket creation error \n");return -1;}serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(SERVER_PORT);// 转换IP地址if(inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr)<=0) {printf("\nInvalid address/ Address not supported \n");return -1;}// 连接服务器if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {printf("\nConnection Failed \n");return -1;}// 构建读取保持寄存器请求uint8_t request[12] = {0};build_modbus_request(request, 0x0000, 10, 0x03);// 发送请求send(sock, request, sizeof(request), 0);// 接收响应uint8_t buffer[BUFFER_SIZE] = {0};int bytes_received = recv(sock, buffer, BUFFER_SIZE, 0);if(bytes_received > 0) {// 验证CRCuint16_t received_crc = (buffer[bytes_received-2]<<8) | buffer[bytes_received-1];buffer[bytes_received-2] = 0;buffer[bytes_received-1] = 0;uint16_t calculated_crc = modbus_crc(buffer, bytes_received-2);if(received_crc == calculated_crc) {printf("Received valid response:\n");for(int i=0; i<bytes_received; i++) {printf("%02X ", buffer[i]);}printf("\n");} else {printf("CRC check failed!\n");}}close(sock);return 0;
}
三、编译与运行
# 编译服务器端
gcc modbus_server.c -o server# 编译客户端
gcc modbus_client.c -o client# 运行服务器
./server# 运行客户端(新终端)
./client
四、关键功能说明
-
协议实现要点 MBAP头处理:包含事务ID、协议ID、长度和单元ID(前7字节) 功能码支持: 0x03:读保持寄存器 0x06:写单个寄存器 0x10:写多个寄存器 CRC16校验:采用Modbus标准算法
-
性能优化技巧 连接复用:保持TCP连接减少握手开销 批量操作:合并多个寄存器读写请求 非阻塞模式:使用
select()
实现多路复用 -
错误处理机制
// 异常响应生成 void send_modbus_error(uint8_t *buffer, uint8_t error_code) {uint8_t response[8] = {0};response[0] = buffer[0]; // 事务IDresponse[1] = buffer[1];response[2] = buffer[2]; // 协议IDresponse[3] = buffer[3];response[4] = buffer[4]; // 长度response[5] = buffer[5];response[6] = buffer[6]; // 单元IDresponse[7] = 0x80 | buffer[7]; // 异常功能码send(client_socket, response, 8, 0); }
五、测试方案
1. 功能测试用例
测试项 | 请求报文 | 预期响应 |
---|---|---|
读保持寄存器 | 00 01 00 00 00 06 01 03 00 00 00 01 | 00 01 00 00 00 02 03 00 0A |
写单个寄存器 | 00 01 00 00 00 06 01 06 00 00 00 01 | 00 01 00 00 00 02 03 00 01 |
写多个寄存器 | 00 01 00 00 00 08 01 10 00 00 00 02 00 0A | 00 01 00 00 00 02 03 00 02 |
2. 压力测试方法
# 使用locust进行压力测试
from locust import HttpUser, task, betweenclass ModbusStressTest(HttpUser):wait_time = between(1, 2.5)@taskdef read_registers(self):self.client.get("/read?addr=0&count=10")
六、扩展功能实现
- 断线重连机制
void reconnect(int *sock) {while(1) {if(*sock < 0) {*sock = socket(AF_INET, SOCK_STREAM, 0);if(connect(*sock, &serv_addr, sizeof(serv_addr)) == 0) {printf("Reconnected successfully\n");break;}}sleep(5);}
}
- 异步通信
// 使用select实现非阻塞通信
fd_set readfds;
struct timeval timeout;FD_ZERO(&readfds);
FD_SET(sock, &readfds);timeout.tv_sec = 5;
timeout.tv_usec = 0;int activity = select(sock+1, &readfds, NULL, NULL, &timeout);
if(activity > 0 && FD_ISSET(sock, &readfds)) {// 处理接收数据
}
参考代码 modbus TCPIP协议client-Server代码 www.youwenfan.com/contentcsj/69522.html
七、完整项目结构
modbus_tcp/
├── server/
│ ├── modbus_server.c
│ └── Makefile
├── client/
│ ├── modbus_client.c
│ └── Makefile
├── tests/
│ ├── test_requests.txt
│ └── stress_test.py
└── docs/└── protocol_spec.md