3. MQTT核心原理(下):保留消息、遗嘱消息、会话机制
好,咱们接着聊MQTT的核心机制。上一节讲了发布/订阅模型和QoS,这一节咱们把剩下的三个重要概念讲透:保留消息、遗嘱消息,还有Clean Session会话机制。
这三个东西,说白了就是MQTT的“贴心小功能”。但用不好,坑也不少。我刚开始做物联网项目时,就在保留消息上吃过亏,后面细说。
3.1 保留消息(Retained Message)
什么是保留消息?
想象一个场景:你有一个温度传感器,每隔10秒发布一次温度数据。新上线的设备想订阅这个温度主题,但它得等10秒才能收到第一条数据。这合理吗?
保留消息就是来解决这个问题的。
当发布者发送消息时,把Retained标志位设为1,这条消息就会被Broker“记住”。任何新订阅者上线,Broker会立刻把这条保留消息推送给它。
核心要点:保留消息是Broker为每个主题保存的“最后一条有价值消息”。
怎么用?
发布时设置保留标志。以ESP8266 + PubSubClient库为例:
// 发布保留消息
client.publish("sensor/temperature", "25.5", true); // 第三个参数就是retained
订阅端完全不用改代码。订阅后自动收到保留消息:
void callback(char* topic, byte* payload, unsigned int length) {
// 这里收到的可能是实时消息,也可能是保留消息
// 你可以通过判断topic来区分,但通常不需要
}
避坑指南:
我曾经在一个智能灯控项目里,把所有状态都设成了保留消息。结果灯坏了,手动关掉后,新设备订阅时收到的是“开”的状态,直接又把灯打开了……
所以记住:
- 保留消息适合静态或缓慢变化的数据,比如设备类型、固件版本
- 不适合高频变化的传感器数据,除非你明确知道自己在做什么
- 删除保留消息:向该主题发布一条空消息,retained设为true
// 删除保留消息
client.publish("sensor/temperature", "", true);
3.2 遗嘱消息(Will Message)
这个机制的名字有点吓人,但功能很实用。
遗嘱消息是什么?
设备在连接Broker时,可以预先设置一条“遗嘱”。如果设备非正常断开(比如掉电、断网),Broker会替它发布这条遗嘱消息。
说白了,就是设备在“临终”前留个话。
怎么设置?
在连接时配置:
// 设置遗嘱消息
// 参数:主题、消息内容、QoS、保留标志
client.setWill("device/status", "offline", 1, true);
// 然后正常连接
client.connect("ESP8266_001");
当设备正常断开时(调用client.disconnect()),Broker不会发布遗嘱。只有非正常断开才会触发。
我个人的习惯用法:
我会配合保留消息一起用。设备上线时发布一条“online”的保留消息,同时设置遗嘱为“offline”。这样任何订阅者都能实时知道设备状态。
void setup() {
// 设置遗嘱
client.setWill("device/status", "offline", 1, true);
if (client.connect("ESP8266_001")) {
// 上线后发布在线状态
client.publish("device/status", "online", true);
}
}
小技巧:遗嘱消息的QoS建议设为1或2。如果设为0,遗嘱可能丢失,那就失去意义了。
注意:遗嘱消息只能设置一次,在连接时。如果想修改,必须断开重连。
3.3 Clean Session 会话机制
这个机制,嗯,有点绕。但理解了就很简单。
Clean Session是什么?
它决定了Broker要不要为客户端“记住”会话状态。会话状态包括:
- 订阅关系(你订阅了哪些主题)
- 未确认的QoS 1/2消息
- 离线期间错过的消息
两种模式:
| Clean Session | 行为 | 适用场景 |
|---|---|---|
| true(默认) | 每次连接都是全新会话。断开后Broker清除所有状态 | 传感器、简单设备 |
| false | Broker保持会话状态。断线重连后恢复订阅和未确认消息 | 需要持久订阅的设备 |
代码示例:
// Clean Session = true(默认)
client.connect("ESP8266_001");
// Clean Session = false(持久会话)
client.connect("ESP8266_001", "username", "password", 0, 0, 0, 0, false);
// 参数说明:最后一个是cleanSession标志
我遇到过的一个坑:
有一次做远程控制项目,设备用Clean Session = false。结果设备重启后,Client ID没变,Broker恢复了旧的会话。但设备程序里重新订阅了主题,导致订阅了两次。每次发布消息,设备收到两份……
所以记住:
- 用Clean Session = false时,确保Client ID唯一且不变
- 设备程序里不要重复订阅已经存在的订阅
- 如果设备程序改了订阅逻辑,最好换一个Client ID,或者先发一条清除会话的消息
重要提醒:Clean Session = false会占用Broker内存。如果设备数量很大(比如上万台),建议用true。否则Broker可能撑不住。
3.4 三者配合使用
这三个机制经常一起用。我总结了一个典型场景:
- 设备上线:用保留消息发布“online”状态
- 设置遗嘱:断线时自动发布“offline”
- Clean Session:控制设备用false,传感器用true
你想想看,这样一套组合拳下来,设备状态管理是不是清晰多了?
好了,这一节的内容就到这。下一节咱们开始动手写代码,把ESP8266连上MQTT Broker,真正跑起来。
本节总结:
- 保留消息:Broker记住最后一条消息,新订阅者立刻收到
- 遗嘱消息:设备非正常断开时,Broker代发消息
- Clean Session:控制Broker是否保持会话状态