MQTT 协议基础:发布/订阅模型、报文结构与 QoS
大家好,我是老周。今天咱们聊聊 MQTT 协议最核心的三个东西:发布/订阅模型、报文结构,还有那个让人又爱又恨的 QoS。说实话,搞车联网这几年,我见过太多人在这上面栽跟头了。你想想看,车在高速上跑着,一条指令没发到位,那可不是闹着玩的。
2.1 发布/订阅模型:解耦的艺术
传统的客户端/服务器模型,说白了就是点对点通信。客户端直接找服务器要数据,耦合得很紧。MQTT 不一样,它用的是发布/订阅模型。我刚开始接触的时候也觉得绕,后来在项目里用顺手了,才发现这东西是真香。
核心角色有三个:
- 发布者(Publisher):只管发消息,不管谁收
- 订阅者(Subscriber):只管收消息,不管谁发
- 代理(Broker):中间人,负责转发
举个例子。车上的 T-Box 采集到车速数据,它只需要把数据发到 vehicle/speed 这个主题上。至于谁在订阅这个主题——是中控屏要显示,还是后台要记录,T-Box 完全不用关心。这就是解耦。
我在做某个车厂的项目时,遇到过一个问题:T-Box 和手机 App 直接通信,结果 T-Box 固件一升级,App 也得跟着改。后来改成 MQTT 的发布/订阅模型,两边都只跟 Broker 打交道,再也不用互相迁就了。
2.2 主题(Topic)与通配符
主题是 MQTT 里的消息分类标签。它是个层级结构,用 / 分隔。比如:
vehicle/vin123456/speed
vehicle/vin123456/engine/temperature
vehicle/vin123456/gps/latitude
订阅的时候可以用通配符,省事儿不少:
+:匹配单层。比如vehicle/+/speed能匹配所有车的车速#:匹配多层。比如vehicle/vin123456/#能匹配这辆车的所有数据
注意:通配符只能用订阅,不能用发布。我曾经见过有人用 vehicle/+/speed 发消息,结果 Broker 直接拒绝了。嗯,这个坑我替你们踩过了。
2.3 MQTT 报文结构:14 种报文类型
MQTT 的报文结构其实挺简洁的。固定头部只有 2 个字节,后面跟着可变头部和有效载荷。我画了张图,你们一看就明白。
固定头部的第一个字节高 4 位表示报文类型。比如 0x30 就是 PUBLISH 报文。第二个字节是剩余长度,表示后面还有多少字节。这个设计很巧妙,能支持最大 256 MB 的消息体。
小技巧:调试的时候,我习惯用 Wireshark 抓包看 MQTT 报文。固定头部的第一个字节一看,就知道是什么类型的报文,排查问题特别快。
2.4 QoS 服务质量等级:三个档位
QoS 是 MQTT 里最容易被误解的概念。说白了,它就是消息送达的可靠性保证。MQTT 定义了三个等级:
| QoS 等级 | 名称 | 发送次数 | 可靠性 | 适用场景 |
|---|---|---|---|---|
| 0 | 至多一次 | 1 次 | 最低 | 传感器数据、日志 |
| 1 | 至少一次 | ≥1 次 | 中等 | 控制指令、状态更新 |
| 2 | 恰好一次 | 2 次(四步握手) | 最高 | 关键指令、OTA 升级 |
QoS 0:至多一次
发出去就不管了,不确认,不重发。说白了就是「尽力而为」。我在项目中一般把 GPS 位置、车速这种高频但允许丢几条的数据用 QoS 0。省带宽,省流量。
QoS 1:至少一次
发送后会等待 PUBACK 确认。如果超时没收到,就重发。这样能保证消息到达,但可能重复。比如车门解锁指令,重复发一次问题不大,但必须得让车收到。
避坑指南:我曾经在某个项目中,把车窗控制指令设成了 QoS 1。结果网络抖动,指令重复发了三次,车窗电机来回动作了三回。嗯,从那以后,凡是执行类指令,我都在应用层加了个去重逻辑。
QoS 2:恰好一次
这是最可靠的等级,用了四步握手:PUBLISH → PUBREC → PUBREL → PUBCOMP。能保证消息既不丢失也不重复。代价就是流量大、延迟高。我一般只在 OTA 升级包、远程点火这种关键操作上用。
来看个 QoS 2 的完整流程:
发布者 Broker 订阅者
| | |
|---- PUBLISH -------->| |
| |---- PUBLISH -------->|
|<---- PUBREC ---------|<---- PUBREC ----------|
|---- PUBREL --------->| |
| |---- PUBREL --------->|
|<---- PUBCOMP --------|<---- PUBCOMP ---------|
| | |
2.5 如何选择 QoS?
很多新手上来就想用 QoS 2,觉得越可靠越好。其实不是这样的。你想想看,车上有几十个传感器,每秒发几百条数据,全用 QoS 2,Broker 不崩才怪。
我个人的经验是:
- 数据采集类(车速、温度、电压):QoS 0,丢了就丢了,下一条补上
- 状态上报类(车门状态、续航里程):QoS 1,允许重复,但不能丢
- 控制指令类(远程锁车、OTA 升级):QoS 2,必须精确一次
记住一句话:QoS 不是越高越好,而是够用就好。在车联网场景下,带宽和延迟都是稀缺资源,别浪费在不需要的地方。
2.6 保留消息与遗嘱消息
这两个特性在车联网里特别实用,我单独拎出来说一下。
保留消息(Retained Message):发布者发消息时把 RETAIN 标志置 1,Broker 就会存下这条消息的最新版本。新订阅者上线后,Broker 会立即推送给它。比如车辆的最后一次 GPS 位置,新设备连上来就能拿到,不用等下一次上报。
遗嘱消息(Will Message):客户端连接时可以在 CONNECT 报文里设置遗嘱。如果客户端异常断开(比如 T-Box 掉电),Broker 就会帮它发布这条遗嘱消息。我在项目中用这个来检测 T-Box 离线,比心跳超时判断快多了。
实战经验:遗嘱消息的主题最好和正常消息分开,比如用 vehicle/vin123456/status 表示在线,vehicle/vin123456/will 表示离线。这样订阅者逻辑清晰,不容易搞混。
好了,MQTT 协议基础就聊到这儿。发布/订阅模型让你解耦,报文结构让你看懂数据,QoS 让你控制可靠性。这三个东西搞明白了,后面讲安全的时候你才能理解为什么有些攻击能得逞,有些防护措施又为什么有效。