3. MQTT协议详解:核心概念与机制

MQTT 这协议,说白了就是为物联网设备量身定做的“传话筒”。它轻量、省电,还能在弱网环境下工作。我最早接触它是在一个智能水表项目里,那时候设备电池得撑三年,HTTP 根本不敢用,一用电池就废。MQTT 的报文头最小只有 2 个字节,这才是真正的“省着花”。

这一节,我们把它最核心的几个概念掰开揉碎讲清楚。你搞懂了这些,后面调设备入网时心里就有底了。

3.1 三个核心角色:Broker、Client、Topic

MQTT 的通信模型是典型的发布/订阅模式。它不像 HTTP 那样客户端直接找服务器要数据,而是通过一个中间人——Broker,来转发消息。

  • Broker(代理服务器):消息的中转站。所有设备都连到它,它负责接收消息,再按规则发给订阅者。我习惯用 Mosquitto 做测试,生产环境用 EMQX 或 VerneMQ 比较多。
  • Client(客户端):可以是传感器、手机 App,或者后端服务。每个 Client 都能发布消息,也能订阅消息。
  • Topic(主题):消息的“地址”。Broker 根据 Topic 决定把消息发给谁。Topic 是分层的,用斜杠隔开,比如 factory/floor1/temperature。

关键点:发布者和订阅者完全解耦。发布者不知道谁会收到消息,订阅者也不知道谁发的。这种设计让系统扩展起来非常灵活。

举个例子:温度传感器发布消息到 sensor/temp,监控平台订阅了 sensor/temp,Broker 就会把消息推过去。整个过程,传感器和监控平台互不认识。

3.2 QoS 等级:消息到底能不能丢?

MQTT 提供了三个服务质量等级,用来控制消息送达的可靠性。我见过不少新手一上来就全用 QoS 2,结果网络开销大得吓人。其实大部分场景 QoS 1 就够了。

QoS 等级 含义 发送次数 适用场景
0 最多发一次,不确认 1 次 传感器高频数据,丢一帧无所谓
1 至少发一次,有确认 至少 1 次(可能重复) 设备状态上报,允许重复
2 恰好发一次,四次握手 严格 1 次 计费指令、开关控制

避坑指南:我曾经在一个智能门锁项目里,把开锁指令用了 QoS 1。结果网络抖动导致重复消息,门锁收到两次开锁指令,虽然第二次是重复的,但逻辑没处理好,门又开了一次。后来我改成 QoS 2,才彻底解决。涉及控制类的指令,别省那点流量。

QoS 2 的流程比较复杂,它用了 PUBLISH、PUBREC、PUBREL、PUBCOMP 四次握手。你想想看,每次发消息都要来回确认四次,网络开销自然大。所以非必要不用 QoS 2。

3.3 保留消息:新设备也能拿到“历史”数据

保留消息是 MQTT 一个很实用的特性。当 Client 发布消息时,如果设置 Retain = true,Broker 就会把这条消息存下来。以后任何新订阅这个 Topic 的 Client,会立刻收到这条保留消息。

为什么要用保留消息?

  • 新设备上线,立刻知道当前状态,不用等下一次更新。
  • 比如温度传感器每隔 5 分钟上报一次,新订阅者不用干等 5 分钟。
  • 设备重启后,能快速恢复现场状态。

我的习惯:每个 Topic 只保留一条最新的消息。如果业务上需要完整历史,还是得靠数据库。保留消息不是日志,别搞混了。

清除保留消息也很简单:往同一个 Topic 发一条空消息,Retain 设为 true,Broker 就会把之前存的删掉。

3.4 遗嘱消息:设备“猝死”时的最后遗言

物联网设备经常掉线,可能是没电了,可能是网络断了,也可能是被人拔了。遗嘱消息就是用来处理这种情况的。

设备在连接 Broker 时,可以设置一个遗嘱 Topic 和遗嘱消息。如果设备非正常断开(比如心跳超时),Broker 就会替它把这条遗嘱消息发出去。

举个例子:一个门磁传感器,正常状态发布 door/status = closed。它设置遗嘱消息为 door/status = offline。一旦它突然掉线,Broker 就会广播 door/status = offline,监控平台就知道这扇门失联了。

注意:设备正常断开连接(发送 DISCONNECT 报文)时,Broker 不会发遗嘱消息。只有非正常断开才会触发。我曾经踩过这个坑,调试时手动断开连接,遗嘱消息没发,我还以为是代码写错了。

3.5 会话状态:断线重连后还能续上

MQTT 的会话状态机制,让设备断线重连后能无缝恢复。这主要靠两个东西:Clean Session 和 Session Expiry Interval。

  • Clean Session = true:每次连接都是全新会话,Broker 不保存任何状态。适合那些不关心历史消息的设备。
  • Clean Session = false:Broker 会保存会话状态,包括订阅关系、未确认的 QoS 1/2 消息。设备重连后,Broker 会把离线期间的消息推给它。

会话状态里存了啥?

  • 客户端的订阅列表
  • 所有未确认的 QoS 1 和 QoS 2 消息
  • 新发布的 QoS 1 和 QoS 2 消息(如果客户端离线)
  • 客户端未收到的遗嘱消息(如果还没触发)

核心逻辑:会话状态是 Broker 的内存开销。如果设备数量很大,每个都保持持久会话,Broker 的内存会吃紧。我建议只有那些需要离线消息的设备才用持久会话,其他设备用 Clean Session。

3.6 知识体系总览

下面这张图把 MQTT 的核心概念串起来了。你可以把它当作一个快速索引,遇到问题回来看看。

MQTT 核心概念体系 Broker Publisher Subscriber Topic QoS 0/1/2 发布消息 推送消息 路由 保障等级 保留消息 遗嘱消息 会话状态 说明:Broker 是核心枢纽,Client 通过 Topic 发布/订阅消息, QoS 控制可靠性,保留/遗嘱/会话状态解决断线场景。

3.7 避坑总结

最后,我把这些年遇到的一些典型问题列出来,你遇到类似情况可以少走弯路。

  • Topic 设计太随意:不要用 topic1、data 这种名字。建议用 项目/设备类型/设备ID/属性 这种分层结构,方便后续扩展。
  • QoS 滥用:能接受丢数据的场景用 QoS 0,需要确认但允许重复用 QoS 1,只有关键控制指令才用 QoS 2。
  • 遗嘱消息不设:很多设备掉线后,平台完全不知道。设个遗嘱消息,至少能知道设备失联了。
  • 会话状态不清理:持久会话会占用 Broker 内存。设备不再上线时,记得手动清除会话。

小技巧:调试 MQTT 时,我习惯用 mosquitto_sub -v -t '#' 订阅所有 Topic,看看设备到底在发什么。这招排查问题特别快。