Modbus TCP协议建立在TCP/IP网络之上,相较于传统的Modbus RTU,它去除了复杂的CRC校验,增加了一个标准的7字节MBAP报头。解析这个报头是进行Modbus TCP通信开发及故障排查的核心基本功。
一、 MBAP报头结构概览
MBAP报头全长固定为7个字节,位于Modbus TCP数据包的最前端。它主要负责解决“这是谁发的”、“发给谁”、“数据有多长”这三个核心问题。
其结构定义如下表所示:
| 字节索引 (偏移量) | 字段名称 | 长度 | 描述 |
|---|---|---|---|
| 0-1 | 事务处理标识符 | 2 字节 | 标识一次通信请求与响应的配对关系 |
| 2-3 | 协议标识符 | 2 字节 | Modbus协议中固定为 0 |
| 4-5 | 长度 | 2 字节 | 后续字节数(单元标识符 + PDU) |
| 6 | 单元标识符 | 1 字节 | 从站地址或网关目标地址 |
二、 字段深度解析
理解每个字段的含义与字节序,是解析报文的关键。Modbus TCP协议统一采用大端模式传输,即高位字节在前、低位字节在后。
1. 解析事务处理标识符
查看 数据包的前两个字节(字节0和字节1)。
这对字段用于配对请求与响应。客户端发起请求时生成一个随机或递增的数字,服务器响应时必须原样返回。例如,请求包前两字节为 00 05,响应包前两字节也必须是 00 05。这在多线程或高并发通信中至关重要,用于区分不同的事务。
- 操作要点:在编程实现时,建议使用全局递增计数器生成此ID,避免重复。
- 数值范围:
0x0000至0xFFFF(0 - 65535)。
2. 解析协议标识符
检查 第3和第4个字节(字节2和字节3)。
在标准的Modbus TCP通信中,这两个字节必须为 00 00。如果收到的数据包中此处非零,说明该数据包可能属于Modbus over TCP/IP以外的协议,或者是错误报文。
- 特殊情况:仅在极少数网关转换场景下可能遇到非零值,但在标准Modbus TCP应用层开发中,直接将其视为固定值
0即可。
3. 解析长度字段
读取 第5和第6个字节(字节4和字节5)。
这是一个至关重要的字段,它告诉接收方“后面还有多少个字节”。它指示的是从“单元标识符”开始到数据包结束的总字节数。
- 计算逻辑:
该字段的值 = 1(单元标识符) + 功能码长度(1字节) + 数据域长度。 - 示例:如果长度字段值为
00 06,表示后续还有6个字节的数据需要读取。
4. 解析单元标识符
确认 第7个字节(字节6)。
这是MBAP报头的最后一个字节,也是Modbus TCP寻址的核心。它对应传统Modbus RTU中的“从站地址”。
- 默认范围:通常为
01至F7(1 - 247)。 - 特殊用途:
00:通常保留或用于广播。FF(255):常用于某些PLC作为默认本机地址。
三、 报文解析实战流程
以下通过一个具体的抓包案例,演示如何从原始十六进制数据中提取MBAP报头信息。假设捕获到的请求报文如下:
00 01 00 00 00 06 01 03 00 00 00 0A
步骤 1:分割报文
按照 7字节的固定长度,将报文切分为MBAP报头与PDU(协议数据单元)两部分。
- MBAP报头:
00 01 00 00 00 06 01 - PDU部分:
03 00 00 00 0A
步骤 2:逐字段解码
按顺序 解析MBAP报头中的四个字段。
-
解析事务处理标识符:
- 原始数据:
00 01 - 解析结果:
1 - 含义:这是第1号通信事务。
- 原始数据:
-
解析协议标识符:
- 原始数据:
00 00 - 解析结果:
0 - 含义:标准的Modbus协议。
- 原始数据:
-
解析长度:
- 原始数据:
00 06 - 解析结果:
6 - 含义:后续还有6个字节的数据(即
01 03 00 00 00 0A)。
- 原始数据:
-
解析单元标识符:
- 原始数据:
01 - 解析结果:
1 - 含义:目标从站地址为1号站。
- 原始数据:
步骤 3:完整性校验
对比 长度字段的值与实际剩余字节数。
- 长度字段声明:6字节。
- 实际剩余字节:
01(单元ID) +03(功能码) +00 00(起始地址) +00 0A(数量) = 6字节。 - 结论:校验通过,报文完整。
四、 解析流程可视化
为了更直观地理解MBAP报头的解析过程,以下是解析逻辑的流程示意:
五、 开发注意事项
在进行底层报文构造或解析代码编写时,需特别注意以下几点。
1. 字节序的处理
切记 Modbus协议使用大端模式。
在C/C++或Java等语言中,直接读取内存通常没问题;但在x86架构的小端模式系统(如某些PLC或特定环境下的Python struct 解包)中,必须进行字节序转换。
- 错误示例:在小端机器上直接将
00 06读取为整数,可能被错误解释为0x0600(1536)。 - 正确操作:使用 高位字节乘以256加上低位字节的方式计算,或使用网络字节序转换函数(如
ntohs)。
2. 长度字段的动态计算
在编写发送程序时,不要 将长度字段写死。
因为PDU的长度是变化的(例如读保持寄存器功能码 03 的请求帧与写多个寄存器 10 的请求帧长度不同),必须先构造好PDU部分,统计其长度加1(单元标识符),再将结果填入MBAP报头的长度字段。
3. TCP粘包处理
避免 假设一次 recv 调用就能读取完整的MBAP报头。
TCP是流式协议,可能会出现粘包或分包现象。正确的处理逻辑是:
- 尝试读取 前7个字节(MBAP头)。
- 解析 长度字段
Length。 - 继续读取 后续
Length个字节的数据。
4. 网关场景下的单元标识符
注意 区分Modbus TCP设备与Modbus TCP转RTU网关。
- 若目标设备是纯TCP设备(如支持以太网的PLC),单元标识符通常忽略或为固定值(如
FF或01)。 - 若通过网关连接RTU从站,单元标识符必须 设置 为对应RTU从站的地址。
六、 常见报文结构对比
为了加深理解,对比不同场景下的MBAP报头构成。
| 场景描述 | 事务ID | 协议ID | 长度 | 单元ID | 后续PDU示例 |
|---|---|---|---|---|---|
| 读1号站线圈状态 | 00 01 |
00 00 |
00 06 |
01 |
01 00 00 00 0A |
| 写5号站单个寄存器 | 00 02 |
00 00 |
00 06 |
05 |
06 00 01 00 03 |
| 读纯TCP设备数据 | 00 03 |
00 00 |
00 06 |
FF |
03 00 00 00 01 |
通过上述表格可以看出,无论PDU内容如何变化,MBAP报头的前4个字节结构保持高度稳定,仅长度和单元标识符根据具体指令动态调整。

暂无评论,快来抢沙发吧!