2. MQTT核心原理(上):发布/订阅模型、主题(Topic)与通配符、服务质量(QoS 0/1/2)详解

好,咱们正式开始啃MQTT的核心原理。这一节内容,说白了就是MQTT的“内功心法”。你后面写代码能不能稳,能不能处理各种奇葩情况,全看这节理解得透不透。

我个人习惯,学一个新协议,先别急着看代码,先把它的“世界观”搞明白。MQTT的世界观,就是三个词:发布、订阅、主题

2.1 发布/订阅模型:告别“点对点”的思维定势

传统的网络通信,比如HTTP,是典型的“请求-响应”模式。客户端问,服务器答。这就像你打电话,一对一,实时性要求高,但耦合也强。

MQTT不一样。它用的是发布/订阅(Pub/Sub)模型。想象一下微信群聊:

  • 发布者(Publisher):就是发消息的人。他不需要知道谁在看,只管往群里(主题)丢消息。
  • 订阅者(Subscriber):就是看消息的人。他只关心自己感兴趣的群(主题),消息来了就能收到。
  • 代理(Broker):就是微信群服务器。它负责接收发布者的消息,然后转发给所有订阅了这个群的订阅者。

你看,发布者和订阅者之间,完全解耦。他们不需要知道对方的存在,也不需要同时在线。这就是MQTT最核心的优势。

核心要点: 发布/订阅模型打破了传统的“客户端-服务器”直连模式,实现了空间、时间、同步的三维解耦。这在物联网场景下,简直是神器。

我在项目中遇到过一种情况:一个传感器节点采集数据,需要同时发给云端数据库、本地显示屏幕和报警系统。如果用HTTP,你得写三个不同的客户端,分别去调用三个接口。用MQTT?传感器只管往“sensor/data”这个主题发一条消息,三个订阅者各自收自己的,代码量直接砍半。

2.2 主题(Topic):MQTT世界的“地址”

主题,就是MQTT消息的“标签”或“地址”。它决定了消息该去哪里,订阅者该听什么。

主题是一个字符串,用“/”分隔层级。比如:

home/livingroom/temperature
factory/line1/machine3/status
sensor/+/temperature

这里有几个设计原则,我建议你记牢:

  • 层级清晰:用“/”划分层级,从宽泛到具体。比如“工厂/车间/设备/参数”。
  • 不要以“/”开头或结尾:虽然协议允许,但容易引起歧义。我个人习惯统一不加。
  • 避免空格和特殊字符:主题里最好只用字母、数字、下划线、横线。否则调试的时候,有你哭的。
  • 区分大小写:“Home/Temp”和“home/temp”是两个完全不同的主题。这一点,我踩过坑。

我的小技巧: 设计主题时,可以按照“范围/位置/设备/属性”的格式来。比如“factory/shenzhen/robot_arm_01/motor_temp”。这样后期扩展和维护,都特别清晰。

2.3 通配符:一招搞定批量订阅

假设你有一个智能家居系统,家里有10个温度传感器。难道你要一个一个去订阅“home/bedroom/temp”、“home/livingroom/temp”……?那也太傻了。

MQTT提供了两个通配符,专门解决这个问题:

通配符 名称 含义 示例
+ 单级通配符 匹配一个层级 home/+/temp 匹配 home/bedroom/temp,但不匹配 home/bedroom/floor/temp
# 多级通配符 匹配剩余所有层级 home/# 匹配 home/bedroom/temphome/livingroom/light/status 等所有以 home/ 开头的主题

举个例子:

  • 订阅 sensor/+/temperature:你会收到 sensor/1/temperaturesensor/2/temperature,但收不到 sensor/1/humidity
  • 订阅 sensor/#:你会收到所有以 sensor/ 开头的消息,不管后面有多少层级。

注意! # 通配符必须放在主题的最后,而且只能出现一次。比如 home/#/temp非法的。我曾经在调试一个农业大棚项目时,就因为多级通配符放错了位置,导致订阅了一堆垃圾消息,排查了半天。

2.4 服务质量(QoS):消息到底靠不靠谱?

物联网环境,网络不稳定是常态。WiFi断连、信号弱、丢包……MQTT怎么保证消息能送到?靠的就是服务质量(QoS)

MQTT定义了三个等级:

QoS等级 名称 可靠性 网络开销 适用场景
0 至多一次 最低 最小 传感器数据(丢一帧无所谓)、日志
1 至少一次 中等 中等 控制指令(必须执行,但允许重复)
2 恰好一次 最高 最大 计费、支付、关键告警

咱们一个一个说:

QoS 0:至多一次(Fire and Forget)

说白了,就是“发出去就不管了”。Broker收到消息后,不会回复任何确认。发送方也不知道消息到底到没到。这是效率最高的方式,但也是最不可靠的。

我建议:大部分传感器数据,用QoS 0就够了。比如温度、湿度,每秒上报一次,丢个一两帧,完全不影响大局。

QoS 1:至少一次(At Least Once)

这个等级,Broker收到消息后,会回复一个PUBACK包。如果发送方没收到PUBACK,就会重发。所以,消息至少会被送达一次,但可能重复

嗯,这里要注意:重复消息,需要接收方自己做去重处理。我在一个智能灯控项目里,就因为这个吃了亏。灯控指令用了QoS 1,结果网络抖动,指令重复发送,灯在那里闪个不停……后来我在应用层加了个消息ID去重,才解决。

QoS 2:恰好一次(Exactly Once)

这是最严格的等级。它通过两次确认(PUBREC、PUBREL、PUBCOMP)来保证消息既不丢失,也不重复。代价就是网络交互次数最多,延迟最大。

为什么会这样?因为MQTT需要确保发送方和Broker之间,以及Broker和接收方之间,都达成“恰好一次”的共识。这背后的协议细节,咱们后面讲代码的时候再细说。

我的建议: 在单片机上,优先使用QoS 0。如果确实需要可靠性,用QoS 1,并在应用层做去重。QoS 2,除非是极其关键的场景(比如门锁控制、支付扣款),否则尽量别用。因为它的内存开销和网络开销,对资源受限的单片机来说,是个不小的负担。

好了,这一节的内容就到这。发布/订阅模型、主题与通配符、QoS,这三个概念是MQTT的基石。你理解了它们,后面看代码、调Bug,心里就有底了。下一节,咱们接着聊MQTT的连接、心跳和遗嘱,这些都是实战中绕不开的坑。