Modbus TCP报文解析实战:从Wireshark抓包到嵌入式组包的完整闭环
你有没有遇到过这样的场景:
PLC和网关之间TCP连接稳如泰山,netstat显示ESTABLISHED,但读寄存器始终超时;
Wireshark里明明看到请求发出去了,响应帧也回来了,可SCADA画面却一片空白;
更诡异的是——换一台HMI就正常,用同一套代码在Linux上跑通,在STM32上却总卡在Length字段校验失败……
这些不是玄学,是Modbus TCP报文结构在“悄悄说话”。而多数人只听见了“能通”,却没听懂它每一字节的语义。
今天,我们不讲RFC文档的漂亮话,也不堆砌协议图谱。我们就打开一个真实抓包文件(来自某国产智能电表+边缘网关现场),逐字节对照、手把手还原:一个标准的“读保持寄存器(0x03)”请求,是如何从socket.send()调用,变成以太网帧里那13个十六进制数字的;服务器又是如何靠这13个字节,精准定位内存地址、取出10个16位寄存器值,并封装成25字节响应的。
这才是工业通信该有的样子:确定、可追溯、无黑盒。
MBAP头:TCP之上的第一道语义门禁
Modbus TCP没有CRC,没有起始符,没有结束符——它把信任完全交给TCP。但它自己加了一层轻量级“应用头”,叫MBAP(Modbus Application Protocol Header),固定7字节。别小看这7字节,它是整个协议能否成立的逻辑锚点。我们拿真实抓包数据说话:
Wireshark显示(十六进制原始流): 1A 2B 00 00 00 05 01 03 00 00 00 0A ↑↑ ↑↑ ↑↑ ↑↑ ↑↑ ↑↑ ↑ ↑↑ ↑↑ ↑↑ ↑↑ TID PID Len UID FC Addr Qty这13字节里,前7字节就是MBAP头。我们一个字段一个字段“掰开揉碎”。
事务标识(Transaction ID):不是序号,是配对密钥
- 位置:Offset 0–1(
1A 2B→ 十进制6699) - 本质:客户端生成的“会话指纹”,仅用于请求与响应匹配。
- 关键破除误区:
- ❌ 它不是TCP序列号,不参与重传控制;
- ❌ 它不表示优先级或时间先后;
- ✅ 它唯一作用:当服务器并发处理5个读请求时,靠这个值把
0x1A2B