C语言实现Modbus TCP/IP协议客户端-服务器,包含完整的报文解析和CRC校验模块:
一、服务器端代码 (modbus_server.c)
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; // 单元ID
response[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);
// 创建socket
if ((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)
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; // 单元ID
buffer[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) {
// 验证CRC
uint16_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;
}
三、编译与运行
c
# 编译服务器端
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()
实现多路复用 -
错误处理机制
c// 异常响应生成 void send_modbus_error(uint8_t *buffer, uint8_t error_code) { uint8_t response[8] = {0}; response[0] = buffer[0]; // 事务ID response[1] = buffer[1]; response[2] = buffer[2]; // 协议ID response[3] = buffer[3]; response[4] = buffer[4]; // 长度 response[5] = buffer[5]; response[6] = buffer[6]; // 单元ID response[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. 压力测试方法
c
# 使用locust进行压力测试
from locust import HttpUser, task, between
class ModbusStressTest(HttpUser):
wait_time = between(1, 2.5)
@task
def read_registers(self):
self.client.get("/read?addr=0&count=10")
六、扩展功能实现
- 断线重连机制
c
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);
}
}
- 异步通信
c
// 使用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
七、完整项目结构
c
modbus_tcp/
├── server/
│ ├── modbus_server.c
│ └── Makefile
├── client/
│ ├── modbus_client.c
│ └── Makefile
├── tests/
│ ├── test_requests.txt
│ └── stress_test.py
└── docs/
└── protocol_spec.md