3、MQTT协议基础:MQTT工作原理、发布/订阅模型、QoS等级、保留消息与遗嘱消息
好,咱们今天聊聊MQTT。说实话,在物联网这个圈子里混,MQTT是绕不开的一个坎儿。我最早接触MQTT是在一个智能农业的项目里,当时要在STM32和云端之间传传感器数据,试了好几种协议,最后还是MQTT最稳。为什么?因为它轻量、省带宽、还支持断线重连。说白了,它就是为物联网这种资源受限、网络不稳定的场景量身定做的。
3.1 MQTT工作原理:一个“中间人”的故事
MQTT的全称是Message Queuing Telemetry Transport,消息队列遥测传输。名字挺长,但原理不复杂。它基于一个核心角色——Broker(代理服务器)。你可以把Broker想象成一个“邮局”。
在传统的客户端-服务器模型里,客户端直接跟服务器对话。但在MQTT里,所有设备都不直接通信。它们只跟Broker说话。设备A发一条消息给Broker,Broker再决定把这条消息转发给谁。
为什么会这样设计?你想想看,如果有一百个传感器,每个都要跟云端直接建立连接,那网络开销得多大?而且一旦云端挂了,所有设备都失联。有了Broker这个中间人,设备只管发消息,至于消息去哪了、谁在收,它不用操心。这种解耦的设计,我在实际项目中深有体会——有一次云端服务器升级,设备端完全没受影响,因为Broker还在正常工作。
核心要点:MQTT是发布/订阅模式,不是请求/响应模式。设备之间不直接通信,所有消息都通过Broker中转。
3.2 发布/订阅模型:谁说话,谁听
MQTT里有两个基本角色:Publisher(发布者)和Subscriber(订阅者)。发布者负责发消息,订阅者负责收消息。它们之间通过一个叫Topic(主题)的东西来关联。
Topic就是一个字符串,比如/sensor/temperature。发布者往这个Topic上发消息,订阅者订阅这个Topic,就能收到消息。一个Topic可以有多个发布者,也可以有多个订阅者。这就是“一对多”的通信模型。
我记得在做一个楼宇自动化项目时,每个楼层的温湿度传感器都往/building/floor/{floor_number}/temperature这个Topic上发数据。中央监控系统订阅了所有楼层的Topic,就能实时看到整栋楼的温度分布。如果某个楼层温度异常,还能单独往那个楼层的控制Topic发指令。这种灵活性,是传统点对点通信做不到的。
我的习惯:Topic命名一定要有层次结构,用斜杠分隔。比如/project/device_type/device_id/data_type。这样方便使用通配符订阅,也方便后期维护。
3.3 QoS等级:消息可靠性的三个档次
MQTT定义了三个QoS(Quality of Service,服务质量)等级。说白了,就是消息传递的可靠性级别。等级越高,越可靠,但网络开销也越大。
| QoS等级 | 名称 | 工作原理 | 适用场景 |
|---|---|---|---|
| 0 | 至多一次 | 发完就忘,不确认,不重发 | 传感器数据(丢一两个没关系) |
| 1 | 至少一次 | 发送后等待确认,超时重发 | 控制指令(必须到达,但允许重复) |
| 2 | 恰好一次 | 四次握手,确保不丢不重 | 计费、支付等关键数据 |
QoS 0:最轻量,发出去就不管了。适合那些丢了也无所谓的场景,比如温度传感器每秒上报一次数据,丢一两个没关系。我在项目中,大部分传感器数据都用QoS 0,省带宽。
QoS 1:发送者会存消息,直到收到Broker的确认。如果超时没收到,就重发。这样能保证消息至少到达一次,但可能重复。我曾经在控制智能灯的项目里用过QoS 1,结果因为网络抖动,灯收到了两次“开灯”指令,闪了一下。嗯,这里要注意,如果你的设备对重复指令敏感,得做去重处理。
QoS 2:最可靠,但也是最慢的。它通过四次握手来确保消息恰好到达一次。我在做支付网关对接时用过QoS 2,因为每一笔交易都不能丢,也不能重复。但代价就是网络开销大,延迟高。
避坑指南:我曾经在一个项目中,所有消息都用QoS 2,结果设备多了之后,Broker负载飙升,消息堆积严重。后来才发现,大部分数据根本不需要这么高的可靠性。所以,按需选择QoS等级,别一刀切。
3.4 保留消息:新来的也能看到历史
保留消息(Retained Message)是MQTT一个很实用的特性。它的作用是:当发布者往某个Topic发消息时,如果设置了retained=true,Broker就会把这条消息存下来。以后任何新订阅这个Topic的客户端,都会立刻收到这条保留消息。
为什么要用保留消息?你想想看,一个温度传感器每隔10秒上报一次数据。如果有个新设备上线,订阅了温度Topic,它得等最多10秒才能收到第一条数据。但如果传感器设置了保留消息,新设备一订阅就能立刻拿到最近一次的温度值。
我在智能家居项目里经常用保留消息。比如,把灯光的状态(开/关)作为保留消息发布。这样,手机App重新连接时,能立刻知道灯是亮着还是关着,不用等设备重新上报。
注意:每个Topic只能保留一条消息。新的保留消息会覆盖旧的。如果发布一条空消息并设置retained=true,可以清除该Topic的保留消息。
3.5 遗嘱消息:设备“临终”的遗言
遗嘱消息(Will Message)听起来有点吓人,但实际很实用。它允许客户端在连接Broker时,预先设置一条消息。如果客户端意外断开连接(比如掉电、网络中断),Broker就会替它发布这条遗嘱消息。
遗嘱消息的格式和普通消息一样,包含Topic、Payload和QoS。设置遗嘱消息时,客户端需要指定这些参数。一旦客户端正常断开(发送DISCONNECT报文),遗嘱消息就不会被发布。
我记得在一个工业监控项目中,有几十个PLC通过MQTT上报状态。每个PLC都设置了遗嘱消息,Topic是/device/{device_id}/status,Payload是offline。这样,一旦某个PLC掉线,监控系统能立刻知道,并触发报警。如果没有遗嘱消息,系统只能通过“长时间没收到心跳”来判断设备离线,那延迟就大了。
我的建议:遗嘱消息的Topic最好和正常状态上报的Topic分开。比如正常状态用/device/status,遗嘱用/device/will。这样订阅端可以分别处理,逻辑更清晰。
3.6 小结:MQTT在STM32项目中的实际考量
好了,MQTT的基础知识就这些。总结一下我的经验:
- Topic设计:要有层次,用斜杠分隔,方便通配符订阅。
- QoS选择:传感器数据用QoS 0,控制指令用QoS 1,关键数据用QoS 2。别滥用。
- 保留消息:适合设备状态、配置信息等需要“新订阅者立刻知道”的场景。
- 遗嘱消息:一定要用。它能让你的系统及时发现设备离线,而不是傻等超时。
在STM32上移植MQTT客户端时,这些概念会直接体现在代码里。比如,连接Broker时要设置遗嘱消息,发布消息时要指定QoS等级和是否保留。下一章,我会带大家实际动手,在STM32上跑通MQTT客户端,把这些理论变成代码。
嗯,今天就先聊到这。有什么问题,咱们下节课再细说。