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
相关推荐
Zwawa17 小时前
用两个按键做一个OLED菜单:先把选择和执行分开
c语言·单片机·嵌入式
爱吃奥利奥_wen21 小时前
SQL Server 基础语法详解:从建库建表到约束与增删改查
服务器·数据库·oracle
心仪久了会沦陷21 小时前
C语言数据结构入门——树与二叉树详解:概念、堆实现与链式遍历
c语言·数据结构·算法
Android系统攻城狮1 天前
Linux FFmpeg深度解析之avio_open调用流程与实战(三十八)
linux·运维·服务器·ffmpeg·音视频进阶·ffmpeg音视频
Severus_black1 天前
【C++初阶】list使用及相关实现,思路顺滑流畅!
c语言·数据结构·c++·stl·list
雪落漂泊1 天前
Linux进程(五):进程地址空间篇
linux·运维·服务器
wdeok1 天前
Socket编程Tcp
服务器·网络·tcp
千舟软件1 天前
报修系统对接企业微信和钉钉的3种方式_千舟报修云
服务器·钉钉·企业微信
希和20271 天前
C语言程序设计题|有序数组插入数字冒泡排序
c语言·数据结构·算法
迪康coolmu2 天前
出差终端数据安全落地实战:基于迪康端点安全一体化管理系统的四道防线实践
运维·服务器·经验分享·笔记·其他·安全·php