MQTT协议详解:发布/订阅模型、报文结构、QoS等级、保留消息与遗嘱消息

MQTT 这个名字,做物联网的应该都不陌生。它轻量、省带宽、支持低带宽高延迟网络,说白了就是为嵌入式设备量身定做的。我在好几个蜂窝模组项目里都用它做数据上报,效果很稳。

这一节,咱们把 MQTT 的核心机制拆开揉碎了讲。你搞懂了这些,后面做混合组网心里就有底了。

发布/订阅模型:不是点对点,是中间人转发

MQTT 跟 HTTP 最大的区别在哪?HTTP 是客户端直接找服务器要东西,MQTT 是客户端把消息扔给一个中间人(Broker),然后 Broker 再转发给订阅了这条消息的客户端。

这个模型有三个角色:

  • 发布者(Publisher):只管发消息,不管谁收。
  • 订阅者(Subscriber):只管收消息,不管谁发。
  • 代理(Broker):核心中转站,负责消息的路由和分发。

你想想看,这样做的好处是什么?解耦。发布者和订阅者不需要知道对方的存在,也不需要同时在线。我在一个智能水表项目里,水表上报数据时网络可能不稳定,但 Broker 一直在线,等网络恢复了,订阅端照样能拿到数据。

核心概念:主题(Topic)

主题是消息的分类标签,用斜杠分层。比如 sensor/temperature/room1。订阅者可以用通配符 +(单层)和 #(多层)来批量订阅。

MQTT 发布/订阅模型 发布者 传感器节点 Broker 消息代理服务器 订阅者 A 云端应用 订阅者 B 手机 App 发布消息 topic: temp/room1 转发消息 转发消息 关键特点 • 发布者和订阅者完全解耦,不需要知道对方存在 • 支持一对多、多对一、多对多通信 • 消息通过主题(Topic)路由,支持通配符订阅

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 这块就不会出大问题。