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
相关推荐
BullSmall4 小时前
Test Matrix:测试矩阵(IT 领域定义 + 设计实践 + 华为场景应用)
运维·服务器
q***16084 小时前
IP地址、子网掩码(NETMASK)和网关(Gateway)
tcp/ip·gateway·智能路由器
liuyao_xianhui5 小时前
版本控制器git--gdb/cgdb
linux·运维·服务器·git
chuxinweihui5 小时前
应用层协议 HTTP
linux·服务器·网络·网络协议·http
RocketJ5 小时前
TCP、Telepathy 和 HTTP 三者关系
网络协议·tcp/ip·http
xu_yule5 小时前
Linux_16(多线程)信号量+基于环形队列的生成消费模型+自选锁+读写锁
linux·运维·服务器
eyuiomvtywn6 小时前
阿里云DNS解析Vercel部署项目的域名
运维·服务器·阿里云
WongKyunban6 小时前
Linux中的线程是什么?
c语言
LaoZhangGong1237 小时前
以太网HTTP数据包格式分析
c语言·stm32·网络协议·http·tcp·arp
HUT_Tyne2657 小时前
Linux 快速入门
linux·运维·服务器