2. MQTT协议基础

MQTT 这个名字,你肯定听过。但说实话,很多人用了很久也只把它当个“轻量级消息推送工具”。

我刚开始接触物联网那会儿,也觉得 MQTT 就是个简化版的 TCP 长连接。直到有一次,我在一个智能路灯项目里,因为没搞懂 QoS 的细节,导致路灯在信号不好的时候反复开关——嗯,从那以后,我就老老实实把协议啃了一遍。

这一章,咱们就把 MQTT 的核心机制拆开揉碎。你搞懂了这些,后面做蓝牙网关的数据桥接,才能心里有底。

1. 发布/订阅模型:解耦的核心

传统的客户端-服务器模型,说白了就是“你问我答”。但物联网场景里,设备数量动不动成百上千,谁有空一直等着被问?

MQTT 用了另一种思路:发布/订阅。

设备之间不直接通信,中间有个 Broker(消息代理)。发布者只管把消息扔给 Broker,订阅者只管从 Broker 拿消息。两边互不知道对方的存在。

核心角色

  • Publisher(发布者):发送消息的一方。比如温度传感器。
  • Subscriber(订阅者):接收消息的一方。比如手机 App。
  • Broker(代理):消息中转站。负责接收、过滤、转发。
  • Topic(主题):消息的分类标签。类似快递上的地址。

我习惯把 Topic 想象成“频道”。发布者往“频道”里喊话,订阅者只听自己关心的频道。这种模型最大的好处是——解耦。发布者不用管谁在听,订阅者不用管谁在说。你想想看,如果哪天你要把温度传感器换成湿度传感器,只需要改发布端,订阅端完全不用动。

MQTT 发布/订阅模型 Publisher 温度传感器 Subscriber 手机 App Subscriber 云端服务器 Broker 消息代理 发布消息 转发 转发 Topic 示例:sensor/temperature 发布者往这个 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 就会替它发布这条遗嘱消息。

遗嘱消息的流程:

  1. 客户端发起 CONNECT 时,带上 Will Topic、Will Payload、Will QoS。
  2. Broker 保存遗嘱,但不发布。
  3. 客户端正常断开(发送 DISCONNECT),Broker 丢弃遗嘱。
  4. 客户端异常断开(网络中断、心跳超时),Broker 发布遗嘱。

实战经验

我在做蓝牙网关时,遗嘱消息设为 gateway/status,payload 为 offline。云端订阅这个 Topic,一旦收到 offline,就知道网关挂了,可以触发告警或切换备用网关。

遗嘱消息的 QoS 建议至少设为 1。如果设成 QoS 0,遗嘱消息本身也可能丢——那就尴尬了。

遗嘱消息触发流程 蓝牙网关 Broker 云端订阅者 ① CONNECT + Will ② 订阅 will/topic ③ 异常断开 ④ 发布遗嘱 ⑤ 转发遗嘱消息

好了,MQTT 的这些核心机制,咱们都过了一遍。发布/订阅模型让你解耦,报文结构让你省流量,QoS 让你在可靠性和性能之间做选择,保留消息和遗嘱消息则解决了“新订阅者”和“异常掉线”这两个实际问题。