C语言实现Modbus TCP/IP协议客户端-服务器

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

四、关键功能说明

  1. 协议实现要点 MBAP头处理 :包含事务ID、协议ID、长度和单元ID(前7字节) 功能码支持 : 0x03:读保持寄存器 0x06:写单个寄存器 0x10:写多个寄存器 CRC16校验:采用Modbus标准算法

  2. 性能优化技巧 连接复用 :保持TCP连接减少握手开销 批量操作 :合并多个寄存器读写请求 非阻塞模式 :使用select()实现多路复用

  3. 错误处理机制

    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")

六、扩展功能实现

  1. 断线重连机制
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);
    }
}
  1. 异步通信
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
相关推荐
一匹电信狗14 分钟前
【C++】哈希表详解(开放定址法+哈希桶)
服务器·c++·leetcode·小程序·stl·哈希算法·散列表
路由侠内网穿透.1 小时前
本地部署网站流量分析工具 Matomo 并实现外部访问
运维·服务器·远程工作
dnpao1 小时前
在服务器已有目录中部署 Git 仓库
运维·服务器·git
冰糖拌面1 小时前
GO写的http服务,清空cookie
服务器·http·golang
超越自己1 小时前
远程连接银河麒麟服务器-xrdp方式
linux·运维·服务器·远程桌面·银河麒麟
Candice_jy1 小时前
vscode运行ipynb文件:使用docker中的虚拟环境
服务器·ide·vscode·python·docker·容器·编辑器
小年糕是糕手2 小时前
【数据结构】常见的排序算法 -- 插入排序
c语言·开发语言·数据结构·学习·算法·leetcode·排序算法
刘一说2 小时前
CentOS Stream 网络故障排查:静态IP丢失、无法访问的完整解决方案
linux·tcp/ip·centos
nassi_2 小时前
文件属性获取与目录IO操作详解
linux·服务器·网络
熊文豪2 小时前
搭建AI资讯早报:AiOnly全球大模型服务+N8N自动化工作流实战
linux·运维·服务器