MQTT协议详解:发布/订阅模型、报文结构、QoS等级、保留消息与遗嘱消息
MQTT 这个名字,做物联网的应该都不陌生。它轻量、省带宽、支持低带宽高延迟网络,说白了就是为嵌入式设备量身定做的。我在好几个蜂窝模组项目里都用它做数据上报,效果很稳。
这一节,咱们把 MQTT 的核心机制拆开揉碎了讲。你搞懂了这些,后面做混合组网心里就有底了。
发布/订阅模型:不是点对点,是中间人转发
MQTT 跟 HTTP 最大的区别在哪?HTTP 是客户端直接找服务器要东西,MQTT 是客户端把消息扔给一个中间人(Broker),然后 Broker 再转发给订阅了这条消息的客户端。
这个模型有三个角色:
- 发布者(Publisher):只管发消息,不管谁收。
- 订阅者(Subscriber):只管收消息,不管谁发。
- 代理(Broker):核心中转站,负责消息的路由和分发。
你想想看,这样做的好处是什么?解耦。发布者和订阅者不需要知道对方的存在,也不需要同时在线。我在一个智能水表项目里,水表上报数据时网络可能不稳定,但 Broker 一直在线,等网络恢复了,订阅端照样能拿到数据。
核心概念:主题(Topic)
主题是消息的分类标签,用斜杠分层。比如 sensor/temperature/room1。订阅者可以用通配符 +(单层)和 #(多层)来批量订阅。
MQTT 报文结构:14种报文,但常用的就几个
MQTT 协议定义了 14 种报文类型。别被这个数字吓到,实际开发中你经常打交道的也就四五种。
每个报文都包含三部分:
- 固定头:所有报文都有,包含报文类型和剩余长度。
- 可变头:某些报文有,比如报文标识符。
- 有效载荷:消息内容本身。
| 报文类型 | 方向 | 说明 |
|---|---|---|
| CONNECT | 客户端 → Broker | 发起连接请求,携带客户端ID、用户名密码等 |
| CONNACK | Broker → 客户端 | 连接确认,返回连接结果 |
| PUBLISH | 双向 | 发布消息,包含主题和载荷 |
| PUBACK | 双向 | QoS 1 的确认报文 |
| SUBSCRIBE | 客户端 → Broker | 订阅主题,携带主题过滤器和 QoS |
| SUBACK | Broker → 客户端 | 订阅确认,返回每个主题的最大 QoS |
| PINGREQ / PINGRESP | 双向 | 心跳保活,检测连接是否正常 |
| DISCONNECT | 客户端 → Broker | 正常断开连接 |
我的经验:在蜂窝模组上,PINGREQ 的间隔设置很关键。设太短浪费流量,设太长可能被运营商 NAT 超时踢掉。我一般设 60 秒,配合模组的低功耗模式,效果不错。
QoS 等级:三种可靠性,按需选择
MQTT 定义了三个服务质量等级。说白了就是消息送达的靠谱程度。
| QoS | 名称 | 发送次数 | 确认机制 | 适用场景 |
|---|---|---|---|---|
| 0 | 至多一次 | 1 次 | 无确认 | 传感器数据上报,丢几条无所谓 |
| 1 | 至少一次 | 至少 1 次 | PUBACK 确认 | 控制指令,必须到达但允许重复 |
| 2 | 恰好一次 | 2 次握手 | PUBREC/PUBREL/PUBCOMP | 计费、交易等关键数据 |
QoS 0 最简单,发出去就不管了。适合温度、湿度这种高频但允许丢失的数据。我在一个环境监测项目里,温度数据每秒上报一次,用的就是 QoS 0,省流量。
QoS 1 保证消息至少到达一次,但可能重复。比如远程开灯,重复一次也没关系,灯已经亮了。
QoS 2 最严格,通过四次握手保证消息不丢不重。但代价也最大,流量消耗是 QoS 0 的好几倍。我建议只在真正需要的地方用,比如设备 OTA 升级的确认消息。
注意:蜂窝模组用 QoS 2 时要小心。网络延迟高、信号不稳定时,四次握手可能超时,反而导致重传风暴。我曾经在一个 NB-IoT 项目里吃过这个亏,后来改成了 QoS 1 + 应用层去重,问题就解决了。
保留消息:新订阅者也能拿到最新数据
保留消息是 MQTT 一个很实用的特性。发布者发消息时设置保留标志,Broker 就会把这条消息存下来。以后任何新订阅者订阅这个主题时,Broker 会立即把保留消息推过去。
这个机制有什么用?举个例子:一个设备的状态信息,比如“门锁状态:已锁”。新客户端上线订阅这个主题时,如果不用保留消息,它得等设备下次上报才能知道状态。用了保留消息,一订阅就能拿到最新状态。
用法很简单,在 PUBLISH 报文里把保留标志位设为 1 就行。清除保留消息也很简单,发一条空消息,保留标志位设为 1,Broker 就会删除之前的保留消息。
我建议:每个设备上线时,先发布一条保留消息声明自己的状态。这样云端或其他设备随时都能知道设备的最新情况,不用轮询。
遗嘱消息:设备异常断线的最后遗言
遗嘱消息,名字听着有点吓人,但功能很实用。设备在连接时(CONNECT 报文里)可以设置一个遗嘱主题和遗嘱消息。如果设备非正常断开(比如掉电、断网),Broker 就会替它发布这条遗嘱消息。
正常断开时,设备发 DISCONNECT 报文,Broker 就不会发遗嘱消息。
这个机制在物联网里太有用了。比如一个智能门锁,如果突然离线了,云端收到遗嘱消息后可以立即告警,或者触发备用方案。
遗嘱消息的配置在 CONNECT 报文的可变头里:
// 伪代码示例:设置遗嘱消息
mqtt_connect_t conn;
conn.will_flag = 1; // 启用遗嘱
conn.will_topic = "device/status"; // 遗嘱主题
conn.will_message = "offline"; // 遗嘱内容
conn.will_qos = 1; // 遗嘱消息的 QoS
conn.will_retain = 1; // 遗嘱消息是否保留
我曾经踩过的坑:遗嘱消息的 QoS 和保留标志要仔细设置。如果遗嘱消息 QoS 设成 0,可能 Broker 还没来得及发出去就丢了。我一般设成 QoS 1,保留标志也设成 1,这样新订阅者也能看到设备最后的状态。
小结
MQTT 的核心机制其实不复杂。发布/订阅模型让你解耦系统,QoS 等级让你在可靠性和效率之间做权衡,保留消息解决新订阅者的数据同步问题,遗嘱消息处理异常断线。把这些搞明白了,后面做多协议混合组网时,MQTT 这块就不会出大问题。