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
相关推荐
墨风如雪2 分钟前
别被“高价建站”劝退了!我跑了多年的 WordPress 架构,一年只花 $25.7
服务器
云飞云共享云桌面35 分钟前
东莞智能装备工厂数字化实践—研发部门10名SolidWorks设计共享一台云主机流畅设计
服务器·自动化·汽车·负载均衡·制造
专注API从业者44 分钟前
Open Claw 京东商品监控选品实战:一键抓取、实时监控、高效选品
java·服务器·数据库
xingyuzhisuan1 小时前
稳定性考验:连续跑7天,哪家云主机不重启、不掉线?
服务器·人工智能·gpu算力
jsons11 小时前
给每台虚拟机设置独立控制台密码
linux·运维·服务器
leo__5201 小时前
IEC 104 协议 C 语言实现
c语言·数据库
云栖梦泽2 小时前
Linux内核与驱动:14.SPI子系统
linux·运维·服务器·c++
啧不应该啊3 小时前
Day1 Python 与 C 的类型区别
c语言·开发语言
zjun10013 小时前
TCP专栏-1.TCP协议概念说明
网络·网络协议·tcp/ip
yipiantian3 小时前
在Claude项目中实现跨目录访问Skills
linux·运维·服务器