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 三者配合使用

这三个机制经常一起用。我总结了一个典型场景:

  1. 设备上线:用保留消息发布“online”状态
  2. 设置遗嘱:断线时自动发布“offline”
  3. Clean Session:控制设备用false,传感器用true

你想想看,这样一套组合拳下来,设备状态管理是不是清晰多了?

好了,这一节的内容就到这。下一节咱们开始动手写代码,把ESP8266连上MQTT Broker,真正跑起来。

本节总结:

  • 保留消息:Broker记住最后一条消息,新订阅者立刻收到
  • 遗嘱消息:设备非正常断开时,Broker代发消息
  • Clean Session:控制Broker是否保持会话状态