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
相关推荐
gwf2164 小时前
RoCEv2无损网络配置实战:PFC与ECN深度调优
网络·网络协议·tcp/ip·udp·tcpdump
q567315236 小时前
人工智能训练数据采集:稳定代理IP高并发方案全解析
人工智能·爬虫·网络协议·tcp/ip·代理模式·代理ip
韩楚风11 小时前
【参天引擎】一次宕机后的数据恢复,让我把 Cantian 持久化与恢复的六大机制全搞明白了
服务器·网络·数据库·分布式·mysql·架构·cantian
云栖梦泽在11 小时前
稳如狗网络工具箱:IP、DNS、WebRTC、测速、连通性和全球延迟检测工具简明介绍
网络·网络协议·tcp/ip·网络安全·性能优化·webrtc
kirs_ur11 小时前
SSD 安全特性 — 加密与 TCG Opal
服务器·网络·安全
智购科技智能售货柜11 小时前
无人售货机连不上服务器?MQTT 频繁掉线排查全流程~YH
运维·服务器
Mortalbreeze12 小时前
深入 Linux Socket 编程:端口号、网络字节序与 struct sockaddr 详解
linux·服务器·网络·c++
躺柒12 小时前
读半导体简史08大型机
linux·服务器·c语言·数据库·unix·ibm
井川廊咏12 小时前
【总集篇】Linux | 如何创建一个 home 目录在 /data 磁盘的 sudo 用户(补档)
linux·运维·服务器
十月的皮皮12 小时前
STM32从零到量产开发:四路继电器工业控制模块开发— 继电器模块bsp_di.h与 bsp_di.c文件
c语言·stm32·stm32cubemx·hal库