Wireshark抓包实战:MQTT协议排障与报文分析
2026/9/16 2:54:30 网站建设 项目流程

连着加了两天班,最后发现是MQTT的QoS参数在捣鬼——这种经历做物联网的人多少都有过。那台设备明明上报了数据,服务端却像没收到一样,日志里全是“publish success”,平台端却一片空白。你怀疑代码、怀疑网络、怀疑服务器,最后打开Wireshark一抓包,三分钟定位问题。这一篇就是Wireshark实战系列的第116篇,主题是MQTT——轻量发布订阅协议,用抓包的角度把它从上到下扒一遍。

MQTT这个名字在物联网、车联网、智能硬件圈子里几乎无人不知,但很多人的理解停留在“发布订阅”四个字上:客户端发消息到主题,订阅者收到消息。真要出了奇怪问题,反而不知道从哪里下手。这篇博文不会只给你讲协议字段,而是带着你从搭建测试环境开始,一步步用Wireshark把CONNECT、SUBSCRIBE、PUBLISH、PUBACK这些报文全拆一遍,再复盘几个真实排障场景。无论你是嵌入式开发者、后端工程师,还是做Node-RED、KepServer这类工具集成的实施人员,这套方法都能直接套用。

1. 为什么偏要用Wireshark啃MQTT:从一次诡异掉线说起

1.1 黑盒排障的窘境:从“发布成功但接收端无反应”的排查开始

几个月前我帮客户排查一个充电桩项目,设备用的是4G模组通过MQTT连接阿里云。现象非常诡异:设备端日志显示TCP连接正常、PUBLISH报文也发出去了,但云端就是收不到数据,偶尔能收到几条,延迟还特别大。代码翻来覆去看了好几遍,Broker那边的订阅关系也确认过没问题,最后连模组的AT指令时序都对了一遍,还是没有头绪。

后来实在没办法,在模组和服务器之间加了一台笔记本做Wireshark抓包,结果一看就愣住了:设备确实在发CONNECT,但服务器返回的CONNACK中有个字段显示“Session Present=0”,而且紧接着设备并没有把之前订阅的主题重新订阅一遍,直接就开始PUBLISH。云端按持久会话的思路去匹配,数据自然进不了订阅通道。这个bug在业务日志里几乎看不出来,因为TCP层一切都正常,应用层却错位了。

这就是MQTT调试最麻烦的地方:你看到的日志是代码写得出来的,网络传输的细节却只有抓包能看到。MQTT的报文结构比HTTP更紧凑,很多关键信息藏在一个字节的Flags里,靠打印日志很难完全覆盖。Wireshark的价值不在于“抓包”这两个字,而在于把一条一条二进制流还原成可读的协议语义,让你能对着报文和代码逐一确认。

1.2 Wireshark在MQTT调试中的定位:它不是抓包工具,是事实还原器

很多人觉得Wireshark就是分析TCP三次握手、看看HTTP请求用的,分析MQTT好像是杀鸡用牛刀。其实恰恰相反,MQTT的调试场景里,Wireshark是少数能从物理网卡一路看到应用层协议的通用工具。

理由有三点:第一,Wireshark对MQTT的支持已经很成熟,比如3.x版本开始对MQTT的字段解析就相当完整,固定报头、可变报头、Properties、Payload都能结构化展示;第二,它不挑客户端,不管你是用MQTTX、Mosquitto客户端命令,还是STM32上的MQTT协议栈、Qt的QMqttClient,只要是走网络的报文都能抓;第三,它能把TCP层的重传、乱序、窗口变化和MQTT报文对应起来,这点对物联网设备尤其重要,因为很多MQTT异常根本不是协议本身的问题,而是底层TCP传输不稳定导致的。

所以我在这个系列里一直强调:遇到MQTT问题,先别急着改代码,先抓包。Wireshark在MQTT调试里不是可有可无的辅助工具,而是把“代码说什么”和“网络上到底传了什么”做对照的事实还原器。

1.3 搭建一个能反复折腾的本地MQTT测试环境

讲抓包之前,得先有一套不会影响线上业务的环境。我的建议是本地跑一个Mosquitto Broker,再用MQTTX或者命令行客户端做收发,这样想怎么折腾都行。

Windows上装Mosquitto很简单,去官网下载安装包,装完在服务里把mosquitto启动就行。验证Broker是否正常,可以打开两个命令行窗口:

窗口A执行订阅:

mosquitto_sub -h 127.0.0.1 -p 1883 -t "test/topic" -v

窗口B执行发布:

mosquitto_pub -h 127.0.0.1 -p 1883 -t "test/topic" -m "hello mqtt"

窗口A能收到消息,环境就算通了。要抓包的话,网卡选Loopback回环地址就行,本地客户端和Broker之间的流量都会经过它。

本地环境的好处是能让你毫无负担地试各种异常:故意不发送CONNECT直接发PUBLISH会怎样?Client ID重复连接会发生什么?把Keep Alive设置得特别短会发生什么?这些问题在线上一旦出错就是事故,在本地就是一条条报文,随便看。

2. MQTT发布订阅机制速览:理解了这套逻辑才能看懂抓包

2.1 MQTT协议的定位与报文类型地图

MQTT全称是Message Queuing Telemetry Transport,消息队列遥测传输。它的设计初衷是给低带宽、高延迟、不可靠网络下的设备做通信,所以报文头非常小,一个固定报头最少只需要2个字节。按我自己的理解,它本质上是一个“在不可靠网络上假装可靠”的协议,靠TCP传输,又在自己这一层做QoS确认和重传。

MQTT控制报文一共有14种,但实际日常工作里最常打交道的就那么几个:CONNECT(连接请求)、CONNACK(连接确认)、PUBLISH(发布消息)、PUBACK(QoS1确认)、PUBREC/PUBREL/PUBCOMP(QoS2三段确认)、SUBSCRIBE(订阅请求)、SUBACK(订阅确认)、PINGREQ/PINGRESP(心跳)、DISCONNECT(断开连接)。

新手很容易被这堆名字吓到,其实规律很强:每个报文都有一个固定的类型值,比如CONNECT是1,CONNACK是2,PUBLISH是3,SUBSCRIBE是8。抓包时看到这些报文类型,整个会话的阶段就清楚了。

阶段客户端发送Broker回复
建立连接CONNECTCONNACK
订阅主题SUBSCRIBESUBACK
发布消息(QoS0)PUBLISH
发布消息(QoS1)PUBLISHPUBACK
发布消息(QoS2)PUBLISHPUBREC → PUBREL → PUBCOMP
保活PINGREQPINGRESP
断开DISCONNECT

这张表建议收藏。遇到问题时先看处在哪个阶段,再往细节里钻。

2.2 发布订阅模型和“请求响应”模型的本质区别

很多从HTTP转过来的人一开始不适应MQTT,是因为它的通信模型不是“请求-响应”,而是“发布-订阅”。请求响应模型里,发消息的人知道谁会接收,接收完还要给个结果。发布订阅模型里,客户端把消息发到一个主题上,它根本不用关心谁订阅了这个主题,Broker负责把消息转发给所有订阅者。

这个模型的优势是解耦。但抓包的时候就要特别注意:客户端和Broker之间的报文只代表“客户端和Broker的关系”,不代表“发布者到订阅者的完整链路”。举个例子,你用Wireshark抓客户端A的包,看到它PUBLISH给Broker并收到PUBACK,只能说明消息到了Broker。至于Broker有没有把消息推给订阅者B,那要看另一端客户端B的抓包结果。

所以抓MQTT包时,建议有条件就在两端同时抓。只抓一端很容易得出错误结论,我在实际排障中吃过不少亏。尤其是消息“发布成功但订阅端收不到”这类问题,很可能不是发布端的问题,而是Broker和订阅端之间的链路出了状况,这时候只看发布端的抓包会完全被误导。

2.3 遗嘱、保留消息与持久会话:抓包时容易被忽略的三个概念

MQTT有三个机制平时写Demo用不到,但工业项目里几乎都会碰到,而且一旦出错,抓包现象非常典型。

第一个是遗嘱消息(LWT)。客户端在CONNECT时可以指定一个遗嘱主题和遗嘱Payload,当Broker发现客户端异常掉线(比如网络断开、心跳超时),就会替这个客户端发布一条遗嘱消息。抓包时,如果你发现Broker在没有任何客户端主动PUBLISH的情况下,突然向某个主题推送了一条消息,多半就是遗嘱在起作用。这在设备状态监测里很常见:设备上线时设遗嘱为“offline”,Broker检测到异常后把状态广播出去。

第二个是保留消息(Retain)。PUBLISH设置Retain标志后,Broker会存住这条消息,新的订阅者一订阅就能立刻收到。抓包时常见场景是:客户端刚发完SUBSCRIBE,还没主动收过消息,Broker就立马回了一条PUBLISH,这很可能就是Broker把保留消息推过来了。

第三个持久会话(Clean Session=false)在MQTT 3.1.1里叫清理会话标志,在MQTT 5.0里改成了Clean Start和Session Expiry Interval。它决定客户端离线时,Broker要不要保存订阅关系和离线消息。我在1.1里遇到的“Session Present=0”就属于这个范畴。抓包时,CONNACK里的Session Present字段特别重要:如果为0,表示Broker没有找到之前的会话,客户端必须重建所有订阅;如果为1,表示Broker恢复了之前的会话,客户端不需要重新订阅。这两者的差异在持久会话场景下非常容易踩坑。

3. 一步步抓包:从CONNECT建立连接到PUBLISH发布消息的完整拆解

3.1 抓包开始前的准备:网卡选择、过滤器预设、TLS预判

打开Wireshark第一步是选网卡。本地测试选Loopback: lo,抓真实设备流量要选设备所在的物理网卡或Wi-Fi网卡。选错网卡最常见的结果是什么都抓不到,因为流量根本不经过你选的接口。

选完网卡别急着点开始,先把过滤器设好。抓MQTT优先用端口过滤,默认1883端口非加密,8883端口是TLS加密。如果业务用了别的端口,请先确认再过滤。

tcp.port == 1883

这个过滤器能过滤出来TCP层的所有流量,但只适用于明文MQTT。如果Broker开了TLS,那你看到的是加密数据,需要按第5章的TLS解密方法处理。千万不要只写mqtt,因为Wireshark只有在解析出MQTT协议层之后才会用mqtt过滤器。如果流量是加密的,mqtt这个显示过滤器什么都匹配不到,很容易让人误以为“没有MQTT流量”。

在“捕获选项”里建议把网卡抓包长度限制设大一点。默认情况下Wireshark会抓完整帧,但某些网卡驱动或抓包工具会截断帧,导致只显示前520字节,后边的Payload全丢了。关于“为什么只能显示520字节而不是2090字节”这个问题,我后面专门有一节细说,这里先说结论:抓包前把Limit each packet to 96 bytes这类选项彻底关掉,或者改成65535。

3.2 CONNECT与CONNACK:连接建立的细节字段

连接阶段是整个MQTT会话的开始。客户端第一个报文必然是CONNECT,Broker回CONNACK。

在Wireshark里选中CONNECT报文,可以在报文树里看到几层信息。最上层是TCP层,然后就是MQTT层。MQTT的固定报头里,第一个字节的高四位是报文类型,第三位是DUP标志,后两位是QoS。这也是抓包时最容易看出问题的点:如果PUBLISH的QoS位和你代码里设置的不一致,处理逻辑就会错位。

展开MQTT层,重点关注Protocol Name、Version、Connect Flags、Keep Alive和Client ID。Connect Flags这个比特字段很关键,它会把Clean Session、Will Flag、Will Qos、Username、Password这些开关逐个展示出来。比如Will Flag=1但Will Qos=0,说明遗嘱消息是以QoS0发送的,如果业务上想保证遗嘱一定送达,这个组合就有问题。

Inspect里我习惯先看Keep Alive时间,再对照实际抓包间隔。比如Keep Alive设了60秒,但两个PINGREQ之间隔了120秒,那就说明业务侧的保活逻辑有bug。这个字段不匹配会导致Broker提前判定客户端失联,概率性掉线基本都从这里来。

Broker回CONNACK后,重点看Return Code。0表示连接成功,其他值表示拒绝原因。有些工程师在代码里把Return Code打了日志,但日志只显示“连接失败”,具体失败原因还是在报文的“Return Code”字段里才有。Wireshark会用红色标记非0的返回码,看到就直接定位。

3.3 SUBSCRIBE与SUBACK:订阅过程到底发生了什么

连接建立之后,客户端要通知Broker自己对哪些主题感兴趣,这就是SUBSCRIBE。SUBSCRIBE报文的可变报头里有一个Packet Identifier,这个ID是客户端分配的,用于和SUBACK进行一对一匹配。

订阅的本质其实是一个“主题过滤器注册”的过程。主题可以带通配符,比如/dev/+/status里的+表示单层通配,/dev/#表示多层通配。抓包时如果看到SUBSCRIBE报文里的主题过滤器是带通配符的,那后续收到的PUBLISH的Topic可能五花八门,这很正常,不要以为是乱发消息。

Wireshark里展开SUBSCRIBE消息,能看到payload部分有一个或多个Topic Filter与Requested QoS的组合。这里的Requested QoS是客户端请求的最大QoS级别。Broker回SUBACK时,每一个主题都会对应一个Return Code,表示订阅是否成功以及最终授予的QoS级别。如果客户端请求QoS1,Broker回复Return Code为0x01,那说明Broker接受了QoS1订阅;如果回复0x80,表示订阅失败。

这里有个经验:SUBACK中的Return Code与PUBLISH报文里的QoS不一定相同。Broker在转发消息给订阅者时,实际使用的QoS是“发布端QoS”和“订阅接收QoS”中较小的那个。比如发布者用QoS1发布,订阅者以QoS0订阅,那么Broker向订阅者转发时用的是QoS0,也就是说订阅者永远不会收到PUBACK。出现“我明明设置了QoS1,怎么没看到PUBACK”的困惑时,先检查是不是订阅端的QoS被降级了。

3.4 PUBLISH报文拆解:Topic、Packet ID、Payload的读写顺序

PUBLISH是MQTT里最核心的报文。固定报头首字节里的三个位特别重要:DUP、QoS、Retain。

  • DUP=1表示这是一条重复投递的消息,通常是因为客户端没收到确认报文,于是把之前发过的消息重新发了一遍。
  • QoS决定了这台报文是否需要确认,以及确认的方式。
  • Retain=1表示这条消息要被Broker保存,供后续订阅者及时获取。

可变报头部分,第一个字段是Topic Name,然后是Packet Identifier(仅当QoS大于0时存在)。Topic Name是UTF-8字符串,Wireshark会在这一行直接显示主题内容。很多工程师喜欢把日志里打出来的topic跟抓包里Wireshark解出的topic做对比,这个动作非常建议做,因为代码变量拼接导致的主题错位问题,在日志里很难发现,但抓包对比一眼就能看出来。

Packet Identifier是QoS1和QoS2报文必须带的字段。客户端每次发送新PUBLISH时,Packet Identifier会递增,Broker确认同一个消息时也会带着相同的Packet Identifier。如果你发现客户端重传PUBLISH时Packet Identifier没有变化,说明这是同一条消息的重复发送,而不是一条新消息。

Payload部分Wireshark默认会显示原始数据,如果Payload是JSON格式,可以在“Protocol Preferences”里设置把Payload按字符串或JSON来解码,方便阅读。我自己的习惯是抓包只看原始报文,Payload内容用MQTTX或自研脚本去校验,因为有些Payload里带了二进制数据,Wireshark文本视图显示不全。

3.5 QoS 0/1/2在抓包里的差异与确认流程

QoS等级是MQTT里最影响抓包解读的概念。

QoS0是最多一次,发完就完事,没有确认报文。抓包场景下,客户端PUBLISH之后不会有任何后续报文,Broker也不会回复。如果业务上要求实时性好、可以容忍偶发丢失,用QoS0没问题,但抓包时看到“只有PUBLISH没有PUBACK”不要惊讶,不是丢了包,是协议本来就不需要确认。

QoS1是最少一次,需要PUBACK确认。客户端发送PUBLISH后,Broker接收到会回一个PUBACK,报文里的Packet Identifier必须和PUBLISH一致。如果客户端在一定时间没收到PUBACK,就会重传这条PUBLISH,并且把DUP置为1。抓包看到DUP=1的报文,说明网络链路或客户端超时设置不合理。

QoS2是恰好一次,也是最复杂的。整个确认流程是PUBLISH → PUBREC → PUBREL → PUBCOMP 四段握手。PUBREC是Broker的接收确认,客户端收到PUBREC后还要再发PUBREL,Broker收到PUBREL后再回PUBCOMP。之所以这样设计,是为了避免在PUBACK阶段出现丢包导致的消息重复。抓包时如果看到QoS2的流程不完整,比如有PUBLISH但没有PUBREC,那通常是网络断链或Broker异常。

4. Wireshark里必须会的MQTT筛选与统计技巧

4.1 显示过滤器:从“全量流量”到“MQTT视图”

抓包完成后,上千条TCP报文密密麻麻,直接找MQTT报文很费力。最基础也最常用的显示过滤器,就是输入mqtt,但要注意它只能过滤已经解析出MQTT协议层的报文。如果全部是TLS加密流量,这个过滤器结果为空。

更精细的过滤方式是用报文类型过滤。MQTT报文类型在Wireshark里有对应的字段,比如mqtt.msgtype == 3表示只看PUBLISH报文,mqtt.msgtype == 8表示只看SUBSCRIBE,mqtt.msgtype == 1表示只看CONNECT。想记数字嫌麻烦的话,可以在Wireshark的协议树里右键报文行,选择“作为过滤器应用”,它会自动生成mqtt.msgtype == 3这样的条件,完全不用背。

还可以按主题过滤。主题字段是mqtt.topic,比如:

mqtt.topic contains "dev/device01"

这条过滤能快速找出所有涉及某个设备主题的PUBLISH和SUBSCRIBE。这个用法在设备规模大、主题命名不规则的场景下尤其好用,否则一条条翻报文能翻到怀疑人生。

4.2 利用统计端点与会话列表排查连接保持问题

Wireshark的“统计”菜单里有两个功能我经常用在MQTT排障上:“端点”和“会话”。

打开“统计 → 端点”,勾选TCP,能看到每个IP和端口之间的流量字节数、数据包数。如果客户端IP的发送数据包数远大于接收数据包数,说明这个方向一直有大量PUBLISH,或者TCP层一直在重传。重传比例过高时,MQTT应用层表现就是消息堆积、延迟增大。通过端点的“错误”和“重传”列,能快速定位到哪条链路质量堪忧。

“会话”里可以看到两个IP之间的所有TCP连接。MQTT客户端每次重连都会新建一条TCP连接,所以如果你发现同一对IP之间出现了几十条TCP会话,每条会话只持续几秒,那基本就是客户端在反复重连。这时候再回去抓CLIENT → CONNECT报文,重点看CONNACK返回码和Keep Alive设置,往往能找到根源。

4.3 结合TCP流追踪还原完整MQTT会话

Wireshark能把一段TCP连接上所有的载荷重新拼出来,在任意一个MQTT报文上右键 → “追踪TCP流”,就能看到这个连接从TCP握手到MQTT交互的完整数据流。

TCP流视图有两种显示方式:左边是客户端发给Broker的,右边是Broker返回的。文本内容看起来像乱码是正常的,因为MQTT报文里有二进制字段。你可以把视图底部的Show data调成Hex dump,对照报文结构一点点看。这个方法对于排查“报文顺序对不对”“有没有重复发送”“有没有半包粘包”非常直观。

还有一个容易忽略的用法:在TCP流视图里能直接看到TCP层有没有乱序或重传。如果在流里出现连续多个TCP Retransmission,且MQTT层下面紧跟的PUBLISH报文有DUP标志,那就可以确认消息重复并不是业务逻辑的问题,而是网络层面丢包触发的应用层重传。

5. 从抓包到定位:真实场景中的排错链路复盘

5.1 场景一:CONNACK一直不回复,客户端反复重连

现象:设备上线后每隔十几秒就掉线重连,服务端日志显示连接不断建立又断开,设备侧代码检查了很多遍,没发现异常。

抓包后我先把过滤器设为tcp.port == 1883,能看到设备每隔一段时间发起TCP三次握手,紧接着发一个CONNECT报文,但Groker迟迟不回CONNACK。再往下看,TCP层开始出现大量Retransmission,随后Broker直接回了RST,连接断开。

这时候问题就跑出了MQTT层,落在了TCP层。CONNACK不回复的原因通常是Broker或中间网络设备的并发连接数满了、防火墙拦截了入方向的MQTT包、或者Broker设置的max_connections达到了上限。顺着抓包再确认一条:握手完成后,Broker有没有发SYN-ACK?如果SYN-ACK都回了但CONNACK没有,要么Broker应用层卡住,要么被防火墙截了。如果SYN-ACK都没回,那问题在TCP层,和MQTT无关。

5.2 场景二:订阅端收不到消息,问题竟然不在MQTT层

现象:发布端日志显示PUBLISH发送成功,Broker也回了PUBACK,但订阅端一直收不到数据。两端各自觉得自己没问题。

这个场景我习惯在发布端和订阅端同时抓包。发布端的包确认PUBLISH已经发到Broker,PUBACK也正常返回。订阅端抓包时,发现客户端甚至都没发出过SUBSCRIBE报文。这就很有意思了,说明订阅端在代码里认为的“订阅成功”只是本地状态,实际上网络层的订阅关系压根没建起来。

进一步查订阅端代码,发现这个客户端在连接时把Clean Session设成了1,但业务层没有在每次连接后重新订阅。正常情况下,Clean Session = 1表示Broker不保存任何会话信息,客户端一断线,所有订阅全部清空,重连后必须重新订阅。代码里只订阅了一次,之后靠“会话恢复”的假设来支撑,结果就是网络一断,订阅就丢了。抓包的SUBSCRIBE记录完美还原了这条链路。

5.3 场景三:QoS1消息重复投递,PUBACK丢失

现象:设备上报状态时,服务端偶尔会收到重复的状态消息,业务层幂等做得不好导致状态覆盖。

抓包后发现客户端PUBLISH(QoS1)发了两遍,两遍的Packet Identifier和Payload完全一样,第二遍的DUP标志为1。正常场景下,PUBLISH发送后Broker应在几十毫秒内回PUBACK。但在抓包里,第一个PUBLISH发出后800毫秒都没等到PUBACK,于是客户端按QoS1的重传策略把同一消息重发了一遍。

问题出在Broker到客户端之间的网络丢包。TCP层可以看到PUBLISH的包虽然发到了,但从Broker返回的PUBACK那个TCP包在中间丢了。此时Broker和客户端在执行"尽力投递"协议:客户端没收到确认就重发,Broker却已经收到第一条消息并转了业务处理,于是业务层自然就收到两条重复消息。这个场景不算Bug,而是QoS1的语义本身就是"至少一次",允许重复。解决方案只能在业务层做幂等,靠Packet Identifier去重。

5.4 场景四:TLS加密的8883端口如何解密分析

很多生产环境为了安全,MQTT走8883端口加TLS加密。抓包一看全是密文,Wireshark显示不出MQTT字段,这是最常见的坑。

Wireshark解密TLS有个前提:你需要有会话的密钥。主要有两种方式。第一种是配置客户端的TLS日志导出密钥。以Mosquitto客户端为例,设置环境变量SSLKEYLOGFILE,它会把TLS握手时的主密钥写入一个文本文件,Wireshark里进入“首选项 → 协议 → TLS”,在“RSA keys list”或“(Pre)-Master-Secret log filename”里填上这个文件路径,就能解密后续所有TLS流量。

第二种是如果Broker本身支持生成密钥文件,那更简单。但实际项目里,我见过很多人产环境根本拿不到密钥,这时候Wireshark最多做TCP层分析,应用层的MQTT字段是解不开的。替代方案是在Broker端开启一个代理或镜像端口,把解密后的流量导出来,或者在网关设备上做一次TLS终止,在明文侧抓包。千万不要试图暴力破解TLS,那几乎不可能。

6. 几个抓包分析时的细节意识与经验补充

6.1 时间列与延迟计算:从报文间隔看链路瓶颈

Wireshark默认显示的时间是相对时间,从抓包开始的第一包算起。分析MQTT时,我经常把时间列切换成“自上一帧显示的时间差”,这样能直接看到每一对报文之间的间隔。比如CONNECT发出后,隔了多久才收到CONNACK?这个间隔超过几百毫秒就值得关注。

计算端到端消息延迟有一个简单方法:找到发布端PUBLISH报文的时间戳,再找到订阅端收到转发PUBLISH的时间戳,两个相减就是消息从发布端到订阅端的整体路径延迟。当然这需要两端时钟同步,或者两端抓包以同一台设备的时钟为基准。如果做不到,那就改成测单段时间:客户端PUBLISH到Broker的PUBACK,这个时间反映的是设备到Broker的网络质量。

还有一个容易被忽略的点是Nagle算法。MQTT报文很小,如果开启了Nagle,小报文会被合并发送,导致单条消息的确认延迟变大。抓包时如果看到客户端发了PUBLISH,但TCP层迟迟没有把数据推出去,而是和后面的PINGREQ粘在一起发,多半就是Nagle在作怪。TCP_NODELAY该开就开,尤其对实时性要求高的场景。

6.2 字节数之谜:为什么有时只显示520字节而不是2090字节

很多人问我:“Wireshark抓到的MQTT报文,显示的总长度只有520字节,但手机抓包能看到2090个字节,这是为什么?”

这里其实是两个问题。第一个是Wireshark的“捕获帧长度”可能被系统截断。如果你用的抓包工具或驱动在底层限制了MTU或快照长度,那么帧的实际载荷超过限制的部分会被丢弃,Wireshark只能显示截断后的大小。解决方法是检查“捕获选项”里的Limit each packet to设置,改为default或65535。

第二个可能是TCP分段。一个大的MQTT消息被TCP切成多个TCP分段传输,Wireshark默认显示的每个TCP段的长度可能只有520字节左右,这是因为网络中间设备的MTU通常为1500字节,扣除IP头和TCP头,应用层最大分段就是1460字节左右。如果业务数据超过这个值,就会分成多包传输。看完整应用层数据,不要只看单个TCP段,而要看TCP流重组后的MQTT报文长度。

6.3 Wireshark与MQTT工具配合:一份分析流程建议

最后分享一下我自己用Wireshark排MQTT问题的标准流程,不一定适合所有人,但能少走很多弯路。

第一步,先在本地复现。别急着在生产环境抓包,先搭一个和线上网络拓扑类似的环境,用MQTTX或Mosquitto工具模拟客户端,确保正常流程下能看到完整报文。

第二步,在原环境抓包,导出pcap文件。导出时最好按连接维度拆开,一个pcap只保留一个TCP会话,这样回溯问题更清晰。

第三步,结合其他工具交叉确认。MQTTX自带的调试界面能看到收发消息的内容,但它看不到TCP层重传。Node-RED里接了MQTT节点的话,也能看到消息流,但同样看不到CONNACK的Return Code。Wireshark负责“链路真相”,其他工具负责“业务内容”,两边对照才能定位到具体层级。

我实际用下来,这套流程解决过很多“听起来完全不可能”的案例。比如有一次Node-RED通过OPC UA转MQTT时,数据发布频率特别高,导致Wireshark抓包里的PUBLISH大量出现TCP快速重传,最终定位到是Node-RED内部队列堆积,而不是MQTT Broker的问题。这种跨层问题,只看MQTT层或者只看业务日志都很难发现。

写在最后的一点体会

这篇围绕MQTT抓包的内容,其实是我在多个项目里踩坑踩出来的总结。刚开始做物联网时,我也迷信业务日志,总觉得“日志没问题就是没问题”,直到有一次被一个丢失的CONNACK坑到加班到半夜,才真正意识到:

网络世界里的“事实”不是你写了什么代码、也不是框架帮你封装了什么,而是网线上真实流动的那一串0和1。Wireshark不会骗人,它会把所有藏起来的细节都晾在阳光下,剩下的就看你怎么读懂它。

所以下次再遇到MQTT诡异的掉线、消息丢、消息重复,别急着改代码,先开Wireshark抓一把包,从CONNECT开始一条一条报文看过去。很多问题,在报文面前就像被扒掉了伪装,三分钟之内就能真相大白。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询