Modbus TCP 协议基础
Modbus 是工业现场最普及的通信协议,而 Modbus TCP 是它在以太网上的实现:把经典的 Modbus 报文直接塞进 TCP 负载,用 TCP 保证传输可靠,省去了 RTU 里的 CRC 校验和从站地址(地址信息由 TCP 连接本身承载)。
与 RS485 上的 Modbus RTU 相比,Modbus TCP 有几个明显差异:没有设备地址限制(每个连接一个从站)、吞吐更高、能同时承载多个连接,但也引入了 TCP 连接管理与并发处理的问题。一个 Modbus TCP 服务端默认监听 502 端口,客户端通过三次握手建立连接后收发报文。
- MBAP事务标识、协议标识、长度、单元标识。
- 功能码03 读保持寄存器、06 写单寄存器。
- 映射寄存器地址与工艺量一一对应。
- 多站轮询调度、超时重试与并发连接。
对工控工程师来说,Modbus TCP 最大的价值是"拿来即用":几乎所有 PLC、仪表、变频器都内置支持,不需要专用驱动,用 TCP 工具就能先手动把寄存器读一遍,再写程序自动化。
MBAP 报文与功能码
Modbus TCP 请求由 MBAP 报文头加 PDU 组成。MBAP 共 7 字节:事务标识(2 字节,客户端自增,用于匹配响应)、协议标识(2 字节,恒为 0)、长度(2 字节,后续字节数)、单元标识(1 字节,用于网关背后的从站号)。PDU 则是功能码加数据。
// 读保持寄存器请求(功能码 03)报文构造
// 事务ID(2) | 协议ID(2)=0 | 长度(2) | 单元ID(1)
// 功能码(1)=0x03 | 起始地址(2) | 寄存器数量(2)
uint8_t req[12] = {
0x00, 0x01, // 事务标识
0x00, 0x00, // 协议标识
0x00, 0x06, // 长度 = 6
0x01, // 单元标识
0x03, // 功能码:读保持寄存器
0x00, 0x00, // 起始地址 0
0x00, 0x0A // 读 10 个寄存器
};
常用功能码就几个:01 读线圈、03 读保持寄存器、04 读输入寄存器、05 写单线圈、06 写单寄存器、16 写多寄存器。保持寄存器可读可写,输入寄存器只读,采集系统绝大多数读 03 和 04。
寄存器映射与字节序
通信本身不关心寄存器代表什么,映射表才是工程的灵魂。每个寄存器地址对应一个工艺量:温度、压力、转速、运行状态位。做系统前先和厂商拿到点表,确认地址、数据类型、量程与字节序,否则读回来的数字对不上现场。
- 地址偏移:Modbus 地址从 0 开始编号,但很多设备用 40001 风格(4xxxx 表示保持寄存器),换算时注意减 1。
- 数据类型:16 位整数、32 位浮点、32 位整数,两个寄存器拼一个浮点是常态。
- 字节序:大端(高字节在前)最常见,但部分设备用字交换,必须按点表核对。
- 缩放:原始值乘量程系数才是工程量,比如 0.1 精度对应值要除以 10。
建议把映射表做成配置文件或数据表,而不是散落在代码里:新增设备、改地址、调系数时只改配置,不动逻辑代码。这样一套驱动可以复用给整条产线的不同设备。
服务端(从站)实现
设备或网关作为 Modbus TCP 服务端时,要能接受多个客户端连接,并正确处理并发读写。核心是把"网络层"和"业务层"分开:网络层解析 MBAP 与功能码,业务层按地址读写真实数据区。
// 服务端读寄存器处理(伪代码)
int handle_read_holding(int start, int count,
uint16_t *out) {
for (int i = 0; i < count; i++) {
int idx = start + i;
if (idx < 0 || idx >= REG_COUNT) {
return MODBUS_ERR_ILLEGAL_ADDR; // 越界
}
out[i] = regs[idx]; // 从真实数据区取值
}
return 0;
}
注意数据一致性:读请求正在打包时,如果后台任务同时改写寄存器,会读到不一致的中间值。常见的做法是数据区用互斥锁或临界区保护,或者把工艺数据做成"发布-快照"机制,保证读到的是一致快照。
客户端轮询与并发
上位机作为客户端时,通常要周期轮询多台设备。轮询调度要解决三个问题:每台设备多久扫一次、一次扫哪些寄存器、超时与重试怎么处理。最简单的模型是串行轮询:一台设备请求发出后,等响应或超时再发下一台,实现简单但吞吐低。
// 多站轮询调度(串行 + 超时重试)
for (int station = 0; station < N; station++) {
send_request(station);
if (wait_response(station, 500) == OK) {
parse_and_store(station);
} else {
retry_count[station]++;
if (retry_count[station] >= 3) {
mark_offline(station); // 连续失败标记离线
retry_count[station] = 0;
}
}
}
设备多、周期紧的时候再上并发:每台设备一个独立连接,各自线程或协程轮询,用事务标识保证请求与响应匹配。并发要小心的是寄存器缓冲区竞争与连接风暴,建议限制最大并发数,并对离线设备做退避(指数退避避免重连风暴)。
网关与跨网段
现场很少全是纯 Modbus TCP 设备:大量老仪表还是 RS485 上的 Modbus RTU。这时需要在网关上做 RTU 转 TCP:TCP 报文里的单元标识就对应 RTU 从站地址,网关把 TCP 请求转发成 RTU 帧,再等 RTU 响应回填。
- 单元标识映射:把 RTU 从站号映射到单元标识,一个 TCP 连接可带多个从站。
- 串口仲裁:多条 TCP 请求争用一条串口,需要排队串行转发。
- 超时换算:RTU 响应慢,TCP 侧超时要放宽,避免客户端先超时。
- 跨网段:默认 502 端口要通,防火墙放行时只对可信来源开放。
网关的转发延迟与并发能力直接决定整条链路的性能。选型时看三点:支持的最大连接数、RTU 轮询频率、是否支持多主站并发,避免一台网关拖垮整个采集系统。
安全、调试与工程实践
Modbus TCP 默认没有任何认证与加密,任何能访问 502 端口的主机都能读写寄存器,这在工业网里是真实风险。安全实践至少做到:设备放在工业专网、防火墙只允许可信主机访问 502、写操作(功能码 05/06/16)做来源白名单、关键写操作加应用层口令。
// 1. 轮询前先手动用 TCP 调试工具读一遍寄存器
// 2. 事务标识必须自增并校验,防止错配响应
// 3. 读浮点时核对字节序,先读两个寄存器拼起来
// 4. 多站并发时限制连接数,离线设备做退避
// 5. 502 端口只对工业网内可信主机开放
Modbus TCP 简单得让人放松警惕,但正是它的简单,让它成为最容易被滥用、也最值得做规范的协议。
把点表、轮询调度、网关与安全规范固定成工程模板,新产线接入时就是套模板的事,通信这块基本不会再踩坑。