news 2026/5/20 1:05:08

ModbusTCP报文格式说明的Wireshark抓包演示教程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ModbusTCP报文格式说明的Wireshark抓包演示教程

深入理解Modbus TCP:从Wireshark抓包看报文结构的本质

在工业自动化现场,你是否遇到过这样的场景?SCADA系统读不到PLC的数据,HMI显示异常,而设备明明通电运行。排查网络、确认IP、检查端口……最后发现是一条Modbus TCP请求发错了寄存器地址。这类问题看似简单,但若缺乏对协议底层的直观认知,往往要耗费大量时间“盲调”。

今天,我们就抛开抽象文档,直接用Wireshark抓包 + 实战代码 + 字节级解析的方式,彻底讲清楚一个核心主题:Modbus TCP报文到底长什么样?它是如何在网络中传输并被解析的?


为什么必须懂报文格式?

Modbus协议诞生于1979年,最初运行在RS-485串行总线上(即Modbus RTU)。随着以太网普及,Modbus TCP应运而生——它把原本跑在串口上的协议,搬到了TCP/IP网络上。

但这不是简单的“换条路走”,而是引入了一个关键结构:MBAP头(Modbus Application Protocol Header),用来适配IP网络环境。

很多开发者只记功能码、知道读寄存器用03,却说不清:
- 报文前6个字节究竟是什么?
- Transaction ID有什么用?
- Length字段为何总是比PDU多1?

这些问题的答案,藏在每一次成功的通信背后。我们通过真实抓包来揭开它。


先看一眼完整的Modbus TCP数据帧

当你在Wireshark里捕获到一条Modbus流量时,看到的是这样一串十六进制数据:

00 01 00 00 00 06 01 03 00 00 00 01

别急着逐位解释,先建立整体认知。这12个字节可以分为两大部分:

部分内容长度
MBAP头管理会话和封装信息7 字节(实际前6字节有效)
PDU(协议数据单元)功能码+参数变长

⚠️ 注意:虽然MBAP定义为7字节,但在TCP流中,Unit ID位于第7字节位置,常被误认为属于PDU。准确地说,MBAP = 前6字节,Unit ID 是第七个字节但逻辑上属于应用层寻址

我们拆开来看。


MBAP头详解:让Modbus能在IP网上“说话”

传统的Modbus RTU依赖串行通信的物理特性进行同步,而TCP是面向连接的多会话机制,因此需要一个新的头部来标识每一次交互。

四个关键字段

字段长度示例值含义说明
Transaction ID2 字节00 01客户端生成,用于匹配请求与响应。就像打电话时的“通话编号”。
Protocol ID2 字节00 00固定为0,表示这是标准Modbus协议。未来扩展可用其他值。
Length2 字节00 06表示后续数据长度(含Unit ID + PDU),单位是字节。
Unit ID1 字节01原Modbus RTU中的从站地址,用于同一链路上区分多个设备。

✅ 关键点:TCP本身不关心业务逻辑,所以Modbus靠Transaction ID来识别对应关系。即使多个请求并发发出,只要ID不同,就能正确归类响应。

举个例子:
- 请求发了 Transaction ID = 5
- 所有响应中找同样ID=5的包
- 匹配成功 → 认为此响应属于该请求

这就是为什么你在Wireshark里能看到“[Response to this query in frame X]”的原因。


PDU部分:真正干活的内容

PDU即Protocol Data Unit,由两部分组成:

[ Function Code ][ Data ]

对于上面的例子:

01 03 00 00 00 01 ↑ ↑ ↑↑ ↑↑ │ │ ││ └─── 数量 = 1 │ │ └────── 起始地址 = 0x0000 (对应40001) │ └───────── 功能码 = 03 → 读保持寄存器 └──────────── Unit ID = 1

功能码常见取值

功能码名称用途
01Read Coils读线圈状态(开关量输出)
02Read Input Discrete读输入触点(开关量输入)
03Read Holding Registers读保持寄存器(最常用)
04Read Input Registers读输入寄存器(模拟量输入)
05Write Single Coil写单个线圈
06Write Single Register写单个保持寄存器
16Write Multiple Registers批量写寄存器

比如你要读取温度传感器的值,通常就是发一个FC=03, 地址=40001, 数量=1的请求。


抓包实战:亲眼看看一次通信全过程

实验准备

  • 工具:Wireshark + Modbus Poll(主站) + Modbus Slave模拟器(从站)
  • 网络:两台PC同属192.168.1.x网段
  • 目标:发起一次读取操作,抓取完整TCP流

启动Wireshark,选择正确的网卡开始监听。过滤条件输入:

tcp.port == 502

或更精准地使用内置协议名:

modbus

你会发现所有Modbus报文都被高亮显示,并自动解析出功能码、地址等信息。


请求报文分析(客户端 → 服务器)

原始数据(去除以太网/IP/TCP头后):

00 01 00 00 00 06 01 03 00 00 00 01

我们按偏移分解:

偏移数据字段解释
0x0000 01Transaction ID第1次请求,ID设为1
0x0200 00Protocol ID标准Modbus,固定为0
0x0400 06Length后续共6字节:1(Unit ID)+5(PDU)
0x0601Unit ID目标设备地址为1
0x0703Function Code读保持寄存器
0x0800 00Start Address从地址0开始(即40001)
0x0A00 01Quantity读1个寄存器

🧠 小技巧:Length = 6 是怎么算出来的?
Unit ID(1) + FC(1) + Addr(2) + Qty(2) = 6 → 所以填00 06


响应报文分析(服务器 → 客户端)

收到的响应可能是:

00 01 00 00 00 05 01 03 02 12 34

分解如下:

字段数据解释
Transaction ID00 01与请求一致,确认匹配
Protocol ID00 00协议正常
Length00 05后续5字节:1(Unit ID)+1(FC)+1(Byte Count)+2(Data)
Unit ID01来自设备1
Function Code03正常响应,读保持寄存器
Byte Count02返回2字节数据
Data12 34寄存器值为 0x1234(十进制4660)

✅ 通信完成!客户端成功获取数据。


自己动手构造报文:C语言实现一个简易客户端

理论懂了,不如亲手造一个请求。以下是一个基于Socket的简化版Modbus TCP客户端片段,展示如何手动组包

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> void create_modbus_tcp_request(unsigned char *buf, int tid, int addr, int count) { // MBAP Header buf[0] = (tid >> 8) & 0xFF; // Transaction ID 高字节 buf[1] = tid & 0xFF; // 低字节 buf[2] = 0x00; buf[3] = 0x00; // Protocol ID = 0 buf[4] = 0x00; buf[5] = 0x06; // Length = 6 buf[6] = 0x01; // Unit ID = 1 buf[7] = 0x03; // Function Code 3 buf[8] = (addr >> 8) & 0xFF; // 起始地址高 buf[9] = addr & 0xFF; // 低 buf[10] = (count >> 8) & 0xFF; // 数量高 buf[11] = count & 0xFF; // 低 } int main() { int sock; struct sockaddr_in server; unsigned char req[12], rsp[256]; sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { perror("socket failed"); return -1; } server.sin_family = AF_INET; server.sin_port = htons(502); inet_pton(AF_INET, "192.168.1.100", &server.sin_addr); if (connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0) { perror("connect failed"); close(sock); return -1; } create_modbus_tcp_request(req, 1, 0, 1); // 读设备1,地址0,数量1 send(sock, req, 12, 0); int len = recv(sock, rsp, sizeof(rsp), 0); printf("Received %d bytes:\n", len); for (int i = 0; i < len; i++) { printf("%02X ", rsp[i]); } printf("\n"); close(sock); return 0; }

🔍 重点提醒:
-Length字段不能错,否则对方可能断连或返回异常;
-Transaction ID建议递增,避免重复导致响应错乱;
- 若批量写入多个寄存器,需动态计算Length和后续字节数。


调试中的常见坑点与应对策略

❌ 问题1:只有请求,没有响应

打开Wireshark一看,确实只有一条请求报文飞出去,然后石沉大海。

排查步骤:
1. 查看TCP三次握手是否成功 → 如果没建立连接,检查IP、子网掩码、防火墙;
2. 是否收到RST包?→ 说明服务端未监听502端口(如程序未启动);
3. 完全无回应?→ 中间交换机ACL拦截,或设备离线。

👉 使用捕获过滤器定位问题:

host 192.168.1.100 and port 502

❌ 问题2:收到功能码0x83

响应的功能码变成了83,这不是错误吗?

其实不然。0x83 = 0x03 | 0x80,这是Modbus规定的“异常响应”标志。

此时后续字节通常是错误码,例如:
-01:非法功能码
-02:非法数据地址
-03:非法数据值
-04:设备忙

这意味着你的请求语法没错,但设备拒绝执行——可能是地址越界,也可能是权限不足。


设计建议:写出健壮的Modbus通信程序

1. Transaction ID管理

  • 每次请求自增1,范围0~65535循环;
  • 多线程环境下使用原子操作或互斥锁保护;
  • 收到响应后及时比对ID,防止错包。

2. Length字段计算务必精确

特别是写多个寄存器时,PDU结构变为:

[FC][Start Addr][Qty][Byte Count][Data...]

其中Byte Count = Qty × 2(每个寄存器占2字节)

那么Length = 1(Unit ID) + 1(FC) + 2(Addr) + 2(Qty) + 1(ByteCnt) + N(Data)

例如写3个寄存器 → Length = 1+1+2+2+1+6 = 13 → 填00 0D

3. 安全性不可忽视

  • Modbus TCP无加密、无认证,切勿暴露在公网
  • 生产环境中应部署防火墙规则,仅允许可信IP访问502端口
  • 高安全需求场景可考虑升级至Modbus/TLS或迁移到OPC UA

4. Wireshark高效调试技巧

  • 右键报文 → Follow → TCP Stream:查看完整对话流程
  • Analyze → Decode As…:强制将某端口流量解析为Modbus
  • Coloring Rules:自定义着色规则,快速识别异常响应

它仍在广泛使用:别轻视这个“老协议”

尽管TSN、OPC UA、MQTT等新架构不断涌现,但在许多工厂车间、楼宇自控、能源管理系统中,Modbus TCP依然是主力通信协议之一

原因很简单:
- 实现成本极低
- 文档公开透明
- 开源库丰富(libmodbus等)
- 几乎所有PLC都原生支持

掌握其报文格式,不只是为了抓包分析,更是为了:
- 快速判断是网络问题还是协议问题
- 在跨厂商设备对接时减少扯皮
- 编写可靠的边缘采集程序
- 为后续开发Modbus网关打基础


如果你正在做工业物联网项目,或者负责工控系统的集成调试,不妨现在就打开Wireshark,抓一次真实的Modbus通信,亲自数一遍那12个字节。

你会发现,那些曾经模糊的概念——Transaction ID、MBAP、PDU——突然变得清晰起来。

而这,正是工程师真正的底气所在。

热词汇总:Modbus TCP、报文格式、MBAP头、PDU、功能码、Transaction ID、Wireshark抓包、TCP 502端口、工业自动化、协议解析、寄存器读取、网络调试、数据帧结构、Modbus Poll、异常响应、Socket编程、Length字段、Unit ID

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/5 21:18:53

终极NDS游戏文件编辑器Tinke:从入门到精通完整指南

还在为无法深入探索NDS游戏内部资源而困扰吗&#xff1f;想要提取游戏中的精美素材却苦于没有合适的工具&#xff1f;Tinke作为专业的NDS游戏文件编辑器&#xff0c;为游戏开发者和技术爱好者提供了完整的解决方案。这款强大的开源工具能够深入解析NDS游戏文件系统&#xff0c;…

作者头像 李华
网站建设 2026/5/11 22:48:13

音频切片终极指南:如何使用audio-slicer快速分割音频文件

音频切片终极指南&#xff1a;如何使用audio-slicer快速分割音频文件 【免费下载链接】audio-slicer 项目地址: https://gitcode.com/gh_mirrors/aud/audio-slicer 音频切片是音频处理中的基础操作&#xff0c;能够将长音频文件按照特定规则分割成多个小片段。audio-sl…

作者头像 李华
网站建设 2026/5/4 15:19:04

工业控制应用中Protel99SE权限配置一文说清

Protel99SE权限配置实战&#xff1a;工业控制设计中的安全协作之道在工业自动化设备的研发现场&#xff0c;你是否曾见过这样的场景&#xff1f;一位助理工程师误删了主电源模块的原理图&#xff0c;导致整个PLC控制板设计回退三天&#xff1b;或者&#xff0c;审核人员发现图纸…

作者头像 李华
网站建设 2026/5/10 21:23:06

5分钟快速上手:B站m4s视频无损转换MP4完整教程

你是否曾为B站视频突然下架而痛心不已&#xff1f;那些精心收藏的教学视频、珍贵纪录片、心仪UP主的内容&#xff0c;难道就永远消失了吗&#xff1f;今天我要分享的这款神器&#xff0c;将彻底解决你的困扰&#xff0c;让你轻松实现m4s到MP4的无损转换。 【免费下载链接】m4s-…

作者头像 李华
网站建设 2026/5/9 4:30:19

三星耳机管理工具:解锁隐藏功能的完整使用指南

三星耳机管理工具&#xff1a;解锁隐藏功能的完整使用指南 【免费下载链接】GalaxyBudsClient Unofficial Galaxy Buds Manager for Windows, macOS, and Linux 项目地址: https://gitcode.com/gh_mirrors/gal/GalaxyBudsClient 还在为官方应用功能受限而烦恼吗&#xf…

作者头像 李华
网站建设 2026/5/16 14:53:47

群晖NAS CPU人脸识别终极解决方案:无需GPU的完整指南

群晖NAS CPU人脸识别终极解决方案&#xff1a;无需GPU的完整指南 【免费下载链接】Synology_Photos_Face_Patch Synology Photos Facial Recognition Patch 项目地址: https://gitcode.com/gh_mirrors/sy/Synology_Photos_Face_Patch 还在为群晖NAS无法使用Synology Pho…

作者头像 李华