这期继续更Wireshark实战系列。前面聊过不少协议的抓包分析,今天把MQTT单独拎出来写一篇,原因很简单:这玩意儿现在太常见了,从嵌入式设备上报数据,到服务端之间做消息流转,再到各种IoT平台对接,十有八九都会碰到它。我见过不少同事排查MQTT问题靠猜,客户端日志打一打,服务器日志翻一翻,实在不行重启一下,其实很多时候用Wireshark在链路上看一眼,几秒钟就能定位问题。
这篇我会从MQTT协议本身的设计逻辑讲起,然后带你把环境搭起来,再用Wireshark把一个完整的MQTT会话从头到尾拆一遍。还包括我实际排障过程中遇到的几个典型场景,比如设备上线后疯狂重连、消息发了但订阅端就是收不到、TLS加密链路怎么抓包分析等等。适合刚接触MQTT的开发同学,也适合那些已经用了一段时间、但一直靠“感觉”调试的人。看完你至少能回答这几个问题:MQTT的QoS到底在干嘛、控制报文长什么样、用Wireshark怎么快速过滤和定位问题。
1. MQTT到底是什么,为什么需要抓包看它
1.1 从一个“推送通知”的小类比说起
MQTT全称是Message Queuing Telemetry Transport,翻译过来是消息队列遥测传输。名字看着长,但核心就是八个字:轻量、发布、订阅。
我习惯用一个“对讲机”的例子来解释。你手里拿着一个对讲机,大家都在同一个频道上,你按下说话键说一句“所有人到食堂集合”,在频道里的所有人都能听到,爱去不去是他们自己的事。你不需要知道具体是谁在听,也不需要等每个人给你回一句“收到”。这就是发布订阅模型,发布者只管发,订阅者只管收,双方完全解耦。
如果换成传统的HTTP请求响应模型,就变成你拿着电话本挨个给人打电话说“食堂集合”,你得知道每个人的号码,还得等对方接听,效率低不说,人一多你的电话根本打不过来。
MQTT就是为这种“一对多、低带宽、不稳定网络”场景设计的。它跑在TCP之上,默认端口1883,报文头非常小,几条消息在同一个连接里来回复用,所以特别适合嵌入式设备、传感器网络、移动端推送这类环境。你要是在一个4G物联网卡上跑HTTP轮询,流量费能教你做人;换成MQTT,一条连接一直挂着,有消息才推,省电省钱还省流量。
1.2 MQTT核心机制速览:QoS、保活、遗嘱、保留消息
MQTT能解决的问题不仅仅是“推送”,它在协议层面还设计了好几个实用机制,这也是它和裸TCP长连接、或者自己造轮子的最大区别。
先说QoS,全称Quality of Service,服务质量。MQTT把消息可靠性分成三个等级:
- QoS 0:发出去就完了,不确认、不重发,可能丢。
- QoS 1:至少送达一次。发送方会收到一个确认,收不到就重发,但可能重复。
- QoS 2:恰好送达一次。通过四次握手保证不丢不重,代价是开销最大。
这个分级设计非常实用。比如传感器上报室内温度,偶尔丢一帧问题不大,用QoS 0就行;但如果是设备告警、控制指令这种关键消息,就得用QoS 1甚至QoS 2,不然指令丢了设备没动作,代价就大了。
再说保活机制。TCP连接自然断开其实很难被对端及时发现,尤其是移动网络下,信号飘忽不定,连接可能已经断了,但对端还傻等着。MQTT里有个Keep Alive字段,客户端在建立连接时告诉服务端“我最多隔多久心跳一次”,默认一般是60秒。如果超过1.5倍时间服务端没收到任何报文,它就可以判断客户端挂了,主动断开。这就是为什么你经常在抓包里看到PINGREQ和PINGRESP这一对报文,本质就是心跳。
还有遗嘱消息(Will Message):客户端在连接时声明一个遗嘱主题和遗嘱内容,如果它后面是非正常掉线(比如网络断开、崩溃),服务端就会替它把遗嘱发出去,告诉别人“这家伙挂了,你们自己看着办”。这个机制在设备在线状态管理里特别有用。
最后是保留消息(Retained Message):发布消息时带一个retain标志,Broker会替你把这条消息存起来,后续新的订阅者上线,立刻就能收到这条最新的消息,而不是只能收到订阅之后才发布的消息。这个特性很适合做“设备当前状态”这类场景。
1.3 为什么现场调试一定离不开Wireshark
协议设计得再好,落到真实网络环境里也会出幺蛾子。客户端说发出去了,服务端说没收到,两边各执一词,这时候你吵不过机器,只能看抓包。
Wireshark的价值在于,它能让你站在网络流量的视角看整个交互过程,而不是只听客户端和服务器的“一面之词”。而且Wireshark对MQTT的支持相当完善,拿到报文后会自动帮你解析出报文类型、主题、消息ID、QoS级别、载荷内容等等,免去了自己翻协议细节的功夫。你可以清楚地看到:CONNECT报文发出去了没有?Broker回CONNACK没有?PUBLISH到达了没有?消息被谁转发给了谁?四舍五入就是协议分析界的监控探头,链路上一放,谁来谁走一目了然。
我自己在帮别人排查MQTT问题时,几乎不敢不先抓包。很多时候问题根本不在应用层,而是TCP握手没完成、Keep Alive过期后断连、消息重复重传、或者报文太大被分片了,这些光看日志是看不出来的,必须看包。
2. 先把环境搭起来:Wireshark与MQTT工具链准备
2.1 工具选型:为什么是Wireshark + Mosquitto + MQTTX
做实验和排障前,环境得先搭好。我的常用组合是三件事:Wireshark负责抓包分析,Mosquitto作为本地Broker(MQTT服务器),MQTTX作为图形化客户端。
Wireshark不用多说了,全平台开源,抓包界的瑞士军刀。如果你还没装,去官网下个稳定版就行,安装时注意把Npcap或者WinPcap组件勾上,不然抓不到包。顺带说一句,Windows下如果Wireshark打不开或者抓不到任何包,八成是Npcap驱动没装好,重装一遍基本能解决。
Mosquitto是Eclipse基金会下的开源MQTT Broker,体积小、安装简单、跨平台,本地做实验完全够用。装好之后一条命令就能启动一个Broker,非常方便。生产环境当然有很多别的选择,比如EMQX、EMQ X、VerneMQ、HiveMQ,还有各种云厂商的IoT平台,但它们的核心协议行为都是一样的,本地用Mosquitto足够复现绝大多数问题。
MQTTX是一个跨平台的MQTT客户端工具,界面清爽,支持填写连接参数、订阅主题、发布消息可视化操作,还能同时建立多个客户端连接。调试的时候特别方便,比nodered的mqtt节点灵活,比命令行工具直观。如果你不用图形界面,Mosquitto自带的mosquitto_pub和mosquitto_sub命令行工具也完全够用。
这里插一句热词里有人提到的“MQTT虚拟串口软件”和“Node-RED实现OPC UA转MQTT”,本质上它们也都是围绕MQTT工具链做的事情。前者是把串口数据和MQTT打通,后者是工业现场把OPC UA的数据再转发成MQTT,场景不一样,但最终分析手段都是抓包看MQTT交互。
2.2 本地复现环境搭建
这里我以Windows环境为例,Mac和Linux的步骤也大差不差。
第一步,安装Mosquitto。Windows下可以直接去官网下载安装包,装完把安装目录加到系统PATH里。如果你装的是旧版本,记得手动建一个配置文件夹,比如C:\mosquitto,并把默认配置文件复制过去,新版一般会自动处理。
第二步,启动Broker。最简单的方式是直接执行:
mosquitto -v-v参数是打印详细日志,建议加上,这样你在Broker端也能看到谁连上了、订阅了啥、收到了啥。默认监听1883端口,不做鉴权,局域网内所有人都能连,本地实验无所谓,但生产环境千万别这么裸奔。
第三步,用MQTTX连接。打开MQTTX,新建一个连接,填:
- Host: 127.0.0.1
- Port: 1883
- Client ID: 随便填,比如test-client-01
点击连接,成功之后左边会出现一个绿色的已连接状态。这时候你可以在Wireshark里抓回路(Loopback)网卡的包,因为Broker和客户端都在本机,流量走的是回环接口。
如果你用的是Mosquitto命令行工具,也可以这样玩:
# 订阅test/topic主题 mosquitto_sub -h 127.0.0.1 -p 1883 -t 'test/topic' -v # 发布消息到test/topic mosquitto_pub -h 127.0.0.1 -p 1883 -t 'test/topic' -m 'hello mqtt'会了这几个工具,后面做实验就顺手多了。
2.3 抓包前的几个细节设置
环境搭好了,抓包前有几个细节容易被忽略,我提个醒。
第一,抓包网卡要选对。本机实验就抓Adapter for loopback traffic capture或直接选Loopback: lo,别傻乎乎抓以太网卡,半天看不到包。真机调试时抓设备所在网段的那个网卡,比如Wi-Fi网卡或以太网网卡。
第二,可以先设置好过滤表达式。打开Wireshark,在过滤栏输入:
mqtt或者更聚焦一点:
tcp.port == 1883这样界面上只显示MQTT相关的流量,不会被各种广播包干扰。两者区别在于:mqtt是按应用层协议过滤(Wireshark识别到是MQTT才展示),tcp.port == 1883是端口过滤(不管协议是不是MQTT,只要端口是1883就展示)。我建议排查时两个都试试,有时候端口对了但Wireshark没识别出是MQTT,这时用端口过滤就能看到原始TCP数据,方便进一步分析。
第三,抓包时长长的话,记得用Ctrl+E暂停或设置文件自动切割,避免内存被撑爆。Wireshark默认把所有包都存内存里,长时间抓包内存占用吓人,建议用环形缓冲区,每条文件比如100MB、共20个文件,自动滚动覆盖。
3. 实战抓包:MQTT完整会话逐包拆解
环境就绪,现在开始正式的抓包分析。这一节我会带你走一遍完整的MQTT会话,从连接、订阅、发布到断开,每个关键节点都看一下Wireshark里实际长什么样。
3.1 过滤表达式与报文概览
先启动抓包,然后用MQTTX连上Broker,订阅一个topic(比如test/topic),再发布一条消息,最后断开连接。这时候停止抓包,你会看到一堆TCP包和几个MQTT控制包。
在Wireshark的过滤栏输入mqtt,界面就会收缩到只有MQTT相关的包。如果mqtt过滤出来是空的,而tcp.port == 1883有数据,说明Wireshark没把流量识别成MQTT,往往是端口不是默认1883,或者写的是tcp但报文字段不符合规范,这时候要看原始报文和TCP payload的十六进制,手动判断。
先说一下MQTT控制报文的通用结构。过了一遍协议,MQTT v3.1.1和v5.0的控制报文都由三部分组成:固定头、可变头、载荷。固定头里第一个字节的高4位表示报文类型,比如:
- 0001(1):CONNECT,客户端发起连接
- 0010(2):CONNACK,服务端确认连接
- 0011(3):PUBLISH,发布消息
- 0100(4):PUBACK,QoS 1确认
- 1000(8):SUBSCRIBE,订阅主题
- 1001(9):SUBACK,订阅确认
- 1100(12):PINGREQ,心跳请求
- 1101(13):PINGRESP,心跳响应
- 1110(14):DISCONNECT,断开连接
知道这个,你看到抓包也能快速判断一个包是什么操作,不用每次都点开看。
3.2 CONNECT与CONNACK:设备上线过程
CONNECT是MQTT客户端和服务端交互的第一步。TCP连接由客户端发起,完成三次握手之后,客户端的第一个MQTT报文就是CONNECT。
在Wireshark里选中标记为MQTT的CONNECT包,下面会展开很多字段:
- Client ID:客户端唯一标识,服务端靠它区分不同设备。
- Keep Alive:保活时间,秒为单位,我实验里设置的是60。
- Clean Session:是否清理会话。如果为true,服务端不保留该客户端的会话状态;为false,断线重连之后可能恢复之前的订阅和离线消息。需要说明的是,MQTT v5.0改名叫Clean Start,概念上有点差别,这里不展开。
- Will Message:遗嘱消息,只有在连接时声明了才有,展开能看到遗嘱主题和内容。
- User Name / Password:用户名密码,可选。
- Payload部分:能看到协议名(MQTT)和协议版本(4代表v3.1.1,5代表v5.0)。
协议版本在兼容性排查里很重要。我遇到过设备用MQTT v3.1连Broker,Broker却只认v3.1.1,直接拒绝连接。后来一抓包,CONNECT里的Protocol Level是3,换成4就通了。
如果连接正常,Broker会回一个CONNACK包,里面有一个Connect Acknowledge Flag字段,还有一个Return Code:
- 0:连接接受
- 1:不接受的协议版本
- 2:标识符被拒绝
- 3:服务器不可用
- 4:用户名或密码错误
- 5:未授权
最容易踩的坑就是看到Return Code不是0,那客户端压根没连上,抓包里一眼就能看出来。
3.3 SUBSCRIBE与SUBACK:订阅主题
连接建立之后,客户端如果想接收某个主题的消息,就要发送SUBSCRIBE报文。SUBSCRIBE里可以同时带多个主题过滤器,每个过滤器可以指定不同的请求QoS。
Wireshark里展开SUBSCRIBE能看到:
- Message ID:消息标识符,客户端生成,唯一的,用于匹配SUBACK。
- Payload部分:主题过滤器和请求的QoS。
Broker处理完会回SUBACK,里面包含返回码,比如0表示QoS 0成功、1表示QoS 1成功、2表示QoS 2成功,0x80表示失败。
这里有个细节:如果客户端订阅成功了,那Broker才会把后续匹配主题的PUBLISH推给你。如果你发现订阅了好久啥都收不到,先回头看看SUBACK的返回码是不是0x80,八成是权限或者主题过滤器写错了。
还有一件事要提一下:主题过滤器的通配符。MQTT支持两级通配符,+匹配单层,#匹配多层。比如订阅home/+/temp能收到home/room1/temp,但收不到home/room1/humidity;订阅home/#则能收到home/下面的所有层级。抓包能看到客户端实际订阅了啥,这个对排查为什么“消息没推过来”很有帮助——有时候不是Broker没推,是订阅的过滤器就没匹配上。
3.4 PUBLISH与QoS交互:消息发布的核心流程
发布消息是最核心的环节,也是最容易出现各种诡异问题的环节。
一个PUBLISH包在Wireshark里展开的字段包括:
- Topic Name:主题,比如test/topic。
- Message ID:消息ID,QoS 0时没有这个ID。
- QoS Level:取值0/1/2。
- Retain Flag:是否保留消息。
- Payload:实际的消息内容,Wireshark会以字符串或者十六进制形式显示。
当QoS为0时,客户端发出PUBLISH之后没有任何确认,一条包完事,丢了就丢了。
当QoS为1时,流程是:客户端发PUBLISH,Broker收到后会回一个PUBACK。这个流程看着简单,但里面的坑在于重传机制。如果客户端发出PUBLISH之后迟迟等不到PUBACK,它会按规则重发相同的PUBLISH,此时你抓包会看到好几个相同Message ID的PUBLISH包连着出现。这就是网上常说的“消息重复”问题的根源之一:如果应用层不自己做去重,订阅端可能会收到同样的消息多次。
当QoS为2时,更复杂一些,完整流程是四步握手:
- 发送方发PUBLISH(QoS=2,带有消息ID)
- 接收方回PUBREC(收到发布,表示接收成功,但还没提交给应用)
- 发送方回PUBREL(发布释放,表示可以完成这次交互了)
- 接收方回PUBCOMP(发布完成,双方才能真正释放消息ID)
在Wireshark里,如果你看到一个 Message ID 同时出现PUBLISH、PUBREC、PUBREL、PUBCOMP这几个包,这就是一个完整的QoS 2交互。实际使用中,如果你发现某个消息ID长时间卡在PUBREC没有后续PUBREL,说明客户端或Broker有一方的逻辑出问题了,大概率是网络抖动导致状态机异常,抓包能帮你精确判断卡在哪一步。
再说Retain Flag。我见过有人往一个带retain的主题上发消息,然后后面新设备一上线就收到一条非常古老的消息,一脸懵。抓包一看,PUBLISH里Retain Flag置1,Broker把旧消息缓存住,后来订阅者上线,Broker立刻把这条消息推出来。排查思路很简单:抓包看订阅者一开始收到的消息是不是Broker推送的保留消息,而不是客户端新发布的,就知道这是哪来的了。
3.5 PINGREQ/PINGRESP与DISCONNECT:保活与下线
前面说过,MQTT客户端和服务端之间有保活机制。Keep Alive时间内如果没有其他报文交换,客户端就要发一个PINGREQ,服务端回PINGRESP。抓包看到这一对包,就说明连接还在正常维持,心跳在按节奏走。
如果抓包里出现了TCP的RST(重置)或者FIN(正常关闭),而之前已经没有PINGREQ/PINGRESP了,通常说明连接已经异常,可能是网络超时被某端杀掉。我之前排查过一个设备频繁掉线的案例:设备端日志显示“连接断开”,服务器端却说客户端主动断开了。抓包一看,真相是设备因为网络原因收不到PINGRESP,误以为Broker挂了,自己主动发了DISCONNECT。后来调整了Keep Alive时间和重连策略,问题就消失了。这个案例说明,光看客户端或服务端日志都容易有盲区,抓包才看到全过程。
正常下线时,客户端会发DISCONNECT报文,服务端收到后回收会话资源。注意,如果客户端直接拔网线、断电、杀进程,Broker只能等Keep Alive超时才能发现。所以你经常看到设备已经掉线了,平台侧要过几十秒才更新状态,这就是在等超时。
4. 真实场景排障:设备消息丢失与重连风暴
4.1 场景描述:STM32+4G模组上云
有个项目是用STM32单片机加一个移远4G模组,通过MQTT把设备数据上报到云端IoT平台。设备端跑了MQTT客户端,软件层面看着一切正常,设备测试时也能偶尔连上发数据。结果部署到现场后问题来了:设备经常上线后没多久就离线,云平台那边总是“设备离线”告警;而且用户反馈数据有丢失,指令下发经常没反应。
这种问题,第一反应就是抓包。我在设备侧用一台笔记本接同一个4G路由的网口镜像流量,在Wireshark上过滤mqtt,把整个连接过程抓了个清清楚楚。
4.2 抓包定位“消息丢失”的完整过程
抓到包之后,我按时间线一帧一帧看:
第一眼注意到的是TCP三次握手正常,因为设备是主动外连,Broker的IP和端口也正常。
但往下翻,CONNECT包里的Keep Alive被设置成了一个很短的秒数,比如10秒。乍一看没什么,但移动网络本身延迟高、信号不稳定,10秒保活意味着设备把“判断自己掉线”的频率调得很高,一旦网络稍有抖动,超过阈值就立刻判定为离线,主动重连。
再往下看,发现设备重连前并没有发DISCONNECT,都是直接TCP RST然后重新建立连接。这种粗暴断连方式,Broker侧会认为客户端异常掉线,如果遗嘱消息设置了,还会触发遗嘱发布。于是云平台那边就看到“设备离线-上线-离线-上线”反复横跳。
关于“消息丢失”,我抓到了更关键的一点:设备上报数据用的QoS 0,指令下发订阅的QoS也是0。在弱网环境下,QoS 0的包丢了就丢了,TCP层会重传,但如果连接已经断了,重传也没用。后来我建议把数据上报提高到QoS 1,平台侧加去重;指令下发这种关键操作至少QoS 1,重要指令可以考虑QoS 2。
这里顺便说一句,有人说“QoS 1一定不丢消息”,这是不对的。QoS 1保证的是“至少送达一次”,但这要求连接是活的、网络是通的。如果连接已经断开,消息在连接修复之前发出,Broker那边如果没建立持久会话(Clean Session为true),消息一样会丢,只是协议尽力重传而已。
排查过程里我还顺手验证了一下设备能不能正常订阅到云平台下发的指令。抓包显示,设备只在上线时订阅过一次,之后重连时因为Clean Session设置成了false,但云平台某些Broker并不保证恢复所有订阅,或者会话在超时后被清理了,结果就是设备以为自己还订阅着,平台也以为设备还订阅着,实际上两边早就不在一个频道上了。这类问题抓包能看得很清楚:设备重连后的SUBSCRIBE有没有发出、SUBACK返回码是什么、平台下发指令时设备连接是否存在。
4.3 TLS加密链路怎么抓包分析
很多生产环境不会用明文1883端口,而是用TLS加密的8883端口。这种链路在Wireshark里默认只能看到TLS握手和加密的Application Data,看不到MQTT明文内容。
如果你想看明文,有两种正规做法:
第一种,如果你有服务端的私钥和证书,可以在Wireshark里配置TLS解密。操作路径是:Edit -> Preferences -> Protocols -> TLS,在RSA keys list里填写服务端IP、端口、协议和私钥文件路径。这样Wireshark就能用私钥解密,自动把TLS内容还原成MQTT明文。这里要说明一下:TLS的密钥交换算法需要匹配,旧版本用RSA密钥交换时这种静态私钥解密是可以的,但新版本普遍用ECDHE这种前向保密算法,服务端私钥无法解密流量。解决办法是配置Pre-Master Secret:在客户端设置SSLKEYLOGFILE环境变量(比如Windows下set SSLKEYLOGFILE=C:\sslkey.log),然后启动支持这种输出的客户端程序,Wireshark里在TLS协议设置里指定这个密钥日志文件,就能实时解密查看。这个思路适用于自研客户端和浏览器的场景,十分实用。
第二种,没有密钥或者不方便解密的,就只能从TLS握手细节、连接模式、发送频率、数据包大小这些间接信息做判断,应用层内容就看不了了。所以我在设计系统时,如果后续需要线上排障,会专门在网关上做一个流量镜像端口,或者直接在设备端临时用一个不加密的调试通道抓一把明文MQTT,分析完再切回TLS。
5. 常见问题速查与避坑心得
5.1 常见问题速查表
| 问题现象 | Wireshark排查要点 | 常见根因 | 解决思路 |
|---|---|---|---|
| 客户端一直连不上 | 看TCP是否完成三次握手 | 防火墙拦截1883端口、Broker没启动 | 先抓TCP确认握手,再查Broker日志 |
| CONNECT发出后无CONNACK | 看确认包的返回码,或是否有TCP RST | Keep Alive太短、鉴权失败、协议版本不匹配 | 调整连接参数,检查Broker配置 |
| 设备反复上线离线 | 看有没有DISCONNECT,还是直接RST | 网络抖动、Keep Alive过短、重连策略没退避 | 延长保活时间,重连加指数退避 |
| 消息发布了订阅端收不到 | 看PUBLISH是否到达Broker,Broker有没有转发给订阅者 | 主题通配符不匹配、QoS 0丢包、订阅失效 | 抓包看转发方向,确认主题和订阅关系 |
| 消息收到了但重复 | 看Message ID相同的PUBLISH出现几次、重传情况 | QoS 1重复投递、应用层未做去重 | 应用层按消息ID去重 |
| 设备下线但平台很久才感知 | 看有没有PINGREQ/PINGRESP,Keep Alive是否过长 | Keep Alive设置过长、遗嘱未配置 | 调整保活,利用遗嘱快速感知离线 |
| 加密链路看不到明文 | 看TLS握手、ClientHello里的SNI | 没有配置解密素材 | 按4.3节配置私钥或预主密钥 |
| Wireshark里看不到MQTT包 | 看tcp.port == 1883是否有数据 | 端口不对、Wireshark未识别 | 先用端口过滤,再人工查看TCP payload |
这个速查表基本覆盖了我日常遇到的大多数问题。实际排查时,我建议大家遵循一个顺序:先确认TCP层(握手、断开、重传),再确认MQTT层的连接和订阅,最后才去分析消息内容。别一上来就死盯着Payload里的那几行数据,很多时候问题根源在下面几层。
5.2 我踩过的坑和几个小技巧
最后分享几个实操心得,都是拿真金白银的时间换来的。
第一,千万别忘抓包的时间点。分析MQTT问题,抓包开始得太晚是大忌。很多关键交互发生在连接建立的几秒内,你看日志发现问题时再去抓包,早期报文早就没了。我的习惯是:一旦怀疑MQTT异常,立刻先开抓包,最好能让抓包工具常驻监控,设好循环覆盖,等复现了再停下分析。尤其是设备随机掉线的场景,不提前抓,根本等不到问题发生。
第二,Wireshark的显示过滤器除了mqtt,还有几个很常用的组合,我也顺手记一下:
mqtt.qos == 1 # 只看QoS 1的消息 mqtt.msgtype == 3 # 只看PUBLISH报文,msgtype对应报文类型 tcp.analysis.retransmission # 看TCP重传 mqtt && mqtt.msgtype == 8 # 过滤PINGREQ查看字段名可以在Wireshark里点击包,看左下角的字段树里实际用的名称,这样过滤表达式更准确。
第三,MQTT over WebSocket的抓包。现在有一部分场景,比如浏览器前端用Vue3的mqtt库、或者Node-RED的网页端mqtt节点,走的是WebSocket而非原生MQTT。这时候Wireshark默认不会在MQTT层解析,你需要叠加过滤websocket来看,或者先看TCP payload里的二进制内容,手动分析。这类问题排查起来更费劲,我建议前端代码里尽量保留调试日志,或者干脆用MQTTX这种原生客户端先跑通,再迁移到浏览器环境,能大幅减少定位成本。
第四,关于Broker的选择,本地实验用Mosquitto没问题,但生产环境用EMQX这类带管理界面的Broker会香很多。它的Web管理后台能看到当前所有客户端连接、订阅关系、消息流转情况,再配合Wireshark抓包,排障效率翻倍。不过无论用哪个Broker,抓包分析的基本功都是一样的。
第五,如果你要长时间抓包,比如抓一晚看设备是否掉线,千万别让Wireshark一直把所有包攒在内存里。实测下来,超过几个G内存占用之后电脑会卡成PPT,而且再大的包文件Wireshark打开也卡。用我之前说的环形缓冲,设置文件大小100MB、数量20个,这样即使抓一晚上也不会爆内存。抓完右键单击一个包,选Follow -> TCP Stream,能看到一条连接上所有MQTT交互的完整对话,这对长会话分析尤其有用。
MQTT的坑看起来千奇百怪,其实剥开看都是围绕连接保活、消息QoS、会话恢复这几件事做文章。抓包看一遍,很多“灵异问题”其实都有明确的技术原因。你在排查时如果遇到过那种怎么都说不通的MQTT问题,不妨把抓包打开,看一眼CONNECT和PUBLISH里那几个字段,八成会有新发现。