4. Modbus协议深度解析(下):Modbus TCP报文结构、MBAP头解析、多从站轮询策略、实际抓包分析
4.1 从串口到以太网:Modbus TCP 的诞生
聊完了 Modbus RTU/ASCII,咱们得把目光转向工业以太网。说实话,串口通信在距离和速度上都有天花板。我早年在一个大型水处理项目中,现场设备分布在几百米范围内,用 RS485 组网,波特率一高就丢包,折腾得够呛。
后来换成了 Modbus TCP,问题迎刃而解。它本质上就是把 Modbus 的报文,封装到 TCP/IP 的肚子里,通过以太网传输。你想想看,以太网的速度、距离、抗干扰能力,都不是串口能比的。
但这里有个关键点:Modbus TCP 去掉了 RTU 里的 CRC 校验。为什么?因为 TCP/IP 协议栈底层已经做了可靠的校验和重传,再加一层 CRC 就多余了。嗯,这个设计很聪明,也省了 CPU 开销。
4.2 Modbus TCP 报文结构:MBAP 头
Modbus TCP 的报文,比 RTU 多了一个 7 字节的头部,叫 MBAP(Modbus Application Protocol Header)。说白了,它就是给 Modbus 报文穿了一件“TCP 外衣”。
MBAP 头的结构如下:
| 字段 | 长度(字节) | 描述 |
|---|---|---|
| 事务处理标识符 | 2 | 用于匹配请求和响应,一般由客户端递增 |
| 协议标识符 | 2 | 固定为 0x0000,表示 Modbus 协议 |
| 长度 | 2 | 后续字节数(从单元标识符开始算起) |
| 单元标识符 | 1 | 相当于 RTU 中的从站地址,范围 0-247 |
加上 MBAP 头之后,整个 Modbus TCP 报文就是:MBAP 头(7 字节) + 功能码(1 字节) + 数据(N 字节)。
我个人习惯在抓包时,先看事务处理标识符。如果请求和响应的这个值对不上,那基本可以断定通信出了问题。我曾经在一个项目中,发现上位机发的请求 ID 是 0x0001,但从站返回的却是 0x0002,结果查了半天,发现是从站固件有 bug。
关键点:协议标识符必须是 0x0000。如果抓包看到其他值,那肯定不是标准的 Modbus TCP,可能是某些厂家魔改的协议。
4.3 多从站轮询策略:别让总线闲着
在 Modbus TCP 网络中,一个客户端(比如 PLC 或上位机)可以同时连接多个服务器(从站设备)。但问题来了:你怎么高效地轮询它们?
我见过不少新手,写了个循环,一个一个发请求,等一个响应回来再发下一个。这在设备少、数据量小的时候还行。一旦从站数量超过 10 个,或者某个从站响应慢,整个轮询周期就会被拖得很长。
更聪明的做法是:流水线轮询。说白了,就是不等响应,连续发送多个请求。TCP 协议栈会自动处理乱序和重传。你想想看,这样总线利用率是不是高多了?
举个例子:
// 伪代码:流水线轮询
for (int i = 0; i < 10; i++) {
send_request(slave_id[i], func_code, start_addr, quantity);
// 不等待响应,立即发送下一个
}
// 然后统一接收响应
for (int i = 0; i < 10; i++) {
response = recv_response(timeout_ms);
process_response(response);
}
但这里有个坑:事务处理标识符必须唯一。如果两个请求用了相同的 ID,你收到响应后就不知道对应哪个请求了。我建议用一个全局计数器,每次发送前自增。
避坑指南:我曾经在一个项目中,流水线轮询时没控制并发数,一口气发了 50 个请求。结果从站 CPU 太弱,直接死机了。后来我加了个信号量,限制最大并发 8 个请求,问题解决。
4.4 实际抓包分析:用 Wireshark 看透 Modbus TCP
理论说再多,不如动手抓一次包。我习惯用 Wireshark,它内置了 Modbus TCP 解析器,非常方便。
下面是一个典型的读保持寄存器请求:
请求报文(十六进制):
00 01 00 00 00 06 01 03 00 00 00 0A
解析:
00 01 — 事务处理标识符(第 1 个请求)
00 00 — 协议标识符(Modbus)
00 06 — 长度(后续 6 个字节)
01 — 单元标识符(从站地址 1)
03 — 功能码(读保持寄存器)
00 00 — 起始地址(0)
00 0A — 寄存器数量(10 个)
响应报文:
响应报文(十六进制):
00 01 00 00 00 17 01 03 14 00 01 00 02 00 03 00 04 00 05 00 06 00 07 00 08 00 09 00 0A
解析:
00 01 — 事务处理标识符(与请求匹配)
00 00 — 协议标识符
00 17 — 长度(23 个字节)
01 — 单元标识符
03 — 功能码
14 — 数据字节数(20 个字节)
00 01 ... 00 0A — 10 个寄存器的值(每个 2 字节)
在 Wireshark 里,你直接看 Modbus 这一层就行。它会自动把 MBAP 头、功能码、数据都解析出来。我一般会加个过滤条件:modbus,这样只显示 Modbus 报文,省得被其他流量干扰。
小技巧:如果抓包发现响应超时,先别急着怀疑从站。看看 TCP 层有没有重传。如果 TCP 一直在重传 SYN,那可能是网络不通。如果 TCP 连接正常,但 Modbus 没响应,那才是从站的问题。
4.5 本章小结
Modbus TCP 的核心就是 MBAP 头。它把串口时代的从站地址,变成了单元标识符,同时去掉了 CRC,靠 TCP/IP 保证可靠性。多从站轮询时,流水线策略能大幅提升效率,但要注意控制并发数和事务 ID 的唯一性。
抓包分析是排查问题的利器。我建议你遇到通信故障时,第一件事就是抓包。看看请求有没有发出去,响应有没有回来,事务 ID 对不对。很多时候,问题就出在这些细节上。