2. MQTT协议基础
MQTT 这个名字,你肯定听过。但说实话,很多人用了很久也只把它当个“轻量级消息推送工具”。
我刚开始接触物联网那会儿,也觉得 MQTT 就是个简化版的 TCP 长连接。直到有一次,我在一个智能路灯项目里,因为没搞懂 QoS 的细节,导致路灯在信号不好的时候反复开关——嗯,从那以后,我就老老实实把协议啃了一遍。
这一章,咱们就把 MQTT 的核心机制拆开揉碎。你搞懂了这些,后面做蓝牙网关的数据桥接,才能心里有底。
1. 发布/订阅模型:解耦的核心
传统的客户端-服务器模型,说白了就是“你问我答”。但物联网场景里,设备数量动不动成百上千,谁有空一直等着被问?
MQTT 用了另一种思路:发布/订阅。
设备之间不直接通信,中间有个 Broker(消息代理)。发布者只管把消息扔给 Broker,订阅者只管从 Broker 拿消息。两边互不知道对方的存在。
核心角色
- Publisher(发布者):发送消息的一方。比如温度传感器。
- Subscriber(订阅者):接收消息的一方。比如手机 App。
- Broker(代理):消息中转站。负责接收、过滤、转发。
- Topic(主题):消息的分类标签。类似快递上的地址。
我习惯把 Topic 想象成“频道”。发布者往“频道”里喊话,订阅者只听自己关心的频道。这种模型最大的好处是——解耦。发布者不用管谁在听,订阅者不用管谁在说。你想想看,如果哪天你要把温度传感器换成湿度传感器,只需要改发布端,订阅端完全不用动。
2. MQTT 报文结构:最小化的网络开销
MQTT 的报文非常紧凑。我做过对比,同样一条数据,用 HTTP 发可能要 200 字节,MQTT 只需要 20 字节。在 NB-IoT 这种按流量收费的场景里,差距就是真金白银。
一个 MQTT 报文由三部分组成:
| 字段 | 长度 | 说明 |
|---|---|---|
| 固定头部 | 2 字节起 | 报文类型 + 剩余长度标志位 |
| 可变头部 | 0~N 字节 | 协议版本、连接标志、心跳间隔等 |
| 有效载荷 | 0~N 字节 | 实际数据内容(Topic、消息体等) |
固定头部里的“剩余长度”字段用了变长编码。小于 128 字节时只占 1 字节,大于 128 字节时扩展。这个设计很巧妙——短消息不浪费,长消息也能装。
我的习惯
调试时用 Wireshark 抓包,直接看固定头部的第一个字节。高 4 位是报文类型,0x30 表示 PUBLISH,0x82 表示 SUBSCRIBE。记熟了,排查问题快很多。
3. QoS 等级详解:消息可靠性的三个台阶
QoS(Quality of Service)是 MQTT 里最容易踩坑的地方。我见过有人所有消息都用 QoS 2,结果 Broker 负载飙升;也见过有人全用 QoS 0,丢包了都不知道。
MQTT 定义了三个等级:
| 等级 | 名称 | 发送次数 | 可靠性 | 典型场景 |
|---|---|---|---|---|
| QoS 0 | 至多一次 | 1 次 | 最低 | 传感器实时数据(丢一帧无所谓) |
| QoS 1 | 至少一次 | ≥1 次 | 中等 | 设备状态上报(允许重复) |
| QoS 2 | 恰好一次 | 2 次(四次握手) | 最高 | 控制指令、支付消息(不能丢不能重) |
QoS 0:发出去就不管了。网络好时没问题,网络差时直接丢。适合温度、湿度这种连续上报的数据。
QoS 1:保证对方收到,但可能收到重复消息。Broker 收到后回复 PUBACK,发布者没收到 ACK 就重发。我曾经在一个项目中,因为没做去重处理,设备收到了两条相同的开关指令,结果继电器“咔咔”连跳两次。
QoS 2:最可靠,也最贵。需要 PUBLISH → PUBREC → PUBREL → PUBCOMP 四次交互。我建议只在关键控制指令上用,比如“打开阀门”、“启动电机”。
避坑指南
我曾经在网关项目里,把蓝牙转 MQTT 的所有消息都设成 QoS 2。结果设备一多,Broker 的 CPU 直接飙到 90%。后来改成:传感器数据用 QoS 0,状态上报用 QoS 1,只有远程控制指令用 QoS 2。系统瞬间稳了。
4. 保留消息:新订阅者也能拿到最新数据
默认情况下,订阅者只能收到订阅之后发布的消息。但有些场景需要“新来的人也能看到最新状态”。比如一个温控面板,你希望它一开机就能显示当前温度,而不是等下一次温度上报。
这就是 保留消息(Retained Message) 的作用。
发布消息时,把 Retain 标志位置 1。Broker 会存下这条消息。以后任何新订阅者订阅这个 Topic 时,Broker 立刻把保留消息推过去。
关键点
- 每个 Topic 只能存一条保留消息。
- 发布一条空消息(payload 为空)并设置 Retain=1,可以清除该 Topic 的保留消息。
- 保留消息不影响实时消息的收发。
我习惯在网关启动时,把蓝牙网关的在线状态作为保留消息发出去。这样云端一订阅,立刻就知道网关是否在线,不用等心跳超时。
5. 遗嘱消息:设备突然掉线怎么办?
物联网设备经常面临断电、断网、死机。如果设备突然失联,云端怎么知道?
MQTT 提供了 遗嘱消息(Will Message) 机制。
设备在连接 Broker 时,可以设置一个遗嘱 Topic 和遗嘱消息。如果设备非正常断开(比如网络断开、心跳超时),Broker 就会替它发布这条遗嘱消息。
遗嘱消息的流程:
- 客户端发起 CONNECT 时,带上 Will Topic、Will Payload、Will QoS。
- Broker 保存遗嘱,但不发布。
- 客户端正常断开(发送 DISCONNECT),Broker 丢弃遗嘱。
- 客户端异常断开(网络中断、心跳超时),Broker 发布遗嘱。
实战经验
我在做蓝牙网关时,遗嘱消息设为 gateway/status,payload 为 offline。云端订阅这个 Topic,一旦收到 offline,就知道网关挂了,可以触发告警或切换备用网关。
遗嘱消息的 QoS 建议至少设为 1。如果设成 QoS 0,遗嘱消息本身也可能丢——那就尴尬了。
好了,MQTT 的这些核心机制,咱们都过了一遍。发布/订阅模型让你解耦,报文结构让你省流量,QoS 让你在可靠性和性能之间做选择,保留消息和遗嘱消息则解决了“新订阅者”和“异常掉线”这两个实际问题。