3. Modbus TCP 协议帧结构:MBAP 报文头与 PDU

Modbus TCP 协议帧是 Modbus 协议在 TCP/IP 网络上的封装形式。与传统的串行链路(RTU/ASCII)不同,TCP 帧去除了校验码(由 TCP 协议保证),并引入了专用的报文头(MBAP)来标识事务和单元。理解帧结构是开发采集程序的基础。

3.1 整体帧结构

一个完整的 Modbus TCP 请求/响应帧由两部分组成:MBAP 报文头(7 字节)和 PDU(协议数据单元,可变长度)。

组成部分 长度 描述
MBAP 报文头 7 字节 事务标识符、协议标识符、长度、单元标识符
PDU ≥ 2 字节 功能码(1 字节)+ 数据(可变)
关键区别:Modbus TCP 帧没有 CRC/LRC 校验,因为 TCP 协议本身已提供可靠传输和校验。MBAP 头中的“长度”字段替代了串行帧中的帧结束判定。

3.2 MBAP 报文头详解

MBAP(Modbus Application Protocol)报文头是 TCP 帧特有的 7 字节头部,用于多事务管理和设备寻址。

字段 字节数 说明 示例值(十六进制)
事务标识符 2 用于请求/响应配对。客户端递增,服务器原样返回。 0x0001
协议标识符 2 固定为 0x0000,表示 Modbus 协议。 0x0000
长度 2 后续字节数(从单元标识符开始到帧尾)。 0x0006
单元标识符 1 用于标识远程从站(类似 RTU 的从站地址)。 0x01
事务标识符的作用:当客户端同时发送多个请求时,通过事务标识符匹配响应。例如,请求 A 使用 0x0001,请求 B 使用 0x0002,服务器返回的响应中会携带对应的事务标识符,客户端据此区分。

3.3 PDU:功能码与数据

PDU(Protocol Data Unit)是 Modbus 协议的核心载荷,与传输介质无关。它由 1 字节功能码和可变长度的数据组成。

  • 功能码(1 字节):指示操作类型,如读线圈(0x01)、读保持寄存器(0x03)、写单个寄存器(0x06)等。
  • 数据(可变):包含起始地址、数量、写入值等参数。响应数据则包含字节计数和实际寄存器值。
方向 功能码 数据内容 示例(十六进制)
请求 0x03 起始地址(2 字节)+ 寄存器数量(2 字节) 03 00 00 00 0A
响应 0x03 字节计数(1 字节)+ 寄存器值(N 字节) 03 14 00 01 00 02 ...

3.4 完整帧示例:读取保持寄存器

以下是一个完整的 Modbus TCP 请求帧,用于读取从站 1 的保持寄存器(起始地址 0,数量 10):

事务标识符   协议标识符   长度       单元标识符   功能码   起始地址   寄存器数量
00 01       00 00       00 06      01          03       00 00      00 0A

对应的响应帧:

事务标识符   协议标识符   长度       单元标识符   功能码   字节计数   寄存器值(20 字节)
00 01       00 00       00 17      01          03       14         00 01 00 02 ...
注意:长度字段的值是“从单元标识符开始到帧尾的字节数”。在请求帧中,长度 = 1(单元标识符)+ 1(功能码)+ 4(数据)= 6(0x0006)。在响应帧中,长度 = 1 + 1 + 1 + 20 = 23(0x0017)。

3.5 MBAP 与 PDU 组合关系图

下图展示了 Modbus TCP 帧的完整结构,以及 MBAP 头与 PDU 的拼接方式:

Modbus TCP 帧结构 MBAP 报文头(7 字节) 事务标识符 2 字节 协议标识符 2 字节 (0x0000) 长度 2 字节 单元标识符 1 字节 PDU(协议数据单元,≥ 2 字节) 功能码 数据(可变长度) 长度字段计算 长度 = 1 (单元标识符) + 1 (功能码) + N (数据字节数)

3.6 请求与响应帧对比

下表对比了读取保持寄存器(功能码 0x03)的请求帧和响应帧结构:

字段 请求帧(客户端 → 服务器) 响应帧(服务器 → 客户端)
事务标识符 客户端生成(如 0x0001) 原样返回
协议标识符 0x0000 0x0000
长度 0x0006 0x0017(示例)
单元标识符 目标从站地址(如 0x01) 原样返回
功能码 0x03 0x03
数据 起始地址(2 字节)+ 数量(2 字节) 字节计数(1 字节)+ 寄存器值(N 字节)
实践要点:在编写采集程序时,务必正确设置事务标识符(用于多线程并发请求)和长度字段(避免粘包问题)。单元标识符通常设为 0x01 或 0xFF(广播),具体取决于从站配置。