参考教程:https://www.bilibili.com/video/BV1L7411c7jw/?spm_id_from=333.1387.favlist.content.click
十、保留消息
1、保留消息的概念及作用
(1)MQTT服务端收到指定为保留消息的MQTT报文后,会将其保存,只要有MQTT客户端订阅该主题,MQTT服务端马上将该主题信息发给它;MQTT服务端收到指定为非保留消息的MQTT报文后,则不会将其保存。
(2)当MQTT服务端收到同一主题的保留消息时,后者将会覆盖前者,当有MQTT客户端订阅该主题时,MQTT服务端会将该主题最新收到的保留消息发给MQTT客户端。
2、发布保留消息的方法
MQTT设备发布的保留消息的流程与发布普通消息的流程十分类似,唯一区别是,在发布保留消息时,MQTT设备需要将PUBLISH报文中retainFlag设置为true;如果要发布非保留消息,那么PUBLISH报文中retainFlag设置为false

3、修改保留消息的方法
每一个主题只能有一个"保留消息",如果MQTT客户端想要更新"保留消息",就需要向该主题发送一条新的"保留消息",这样MQTT服务端会将新的"保留消息"覆盖旧的"保留消息",当有MQTT客户端订阅该主题时,MQTT服务端就会将最新的"保留消息"发送给订阅的MQTT客户端了
4、删除保留消息的方法
如果要删除一个主题的"保留消息",可以向该主题发布一条空的"保留消息",也就是发送一条0字节payload的"保留消息"
5、ESP8266保留消息应用
(1)先前介绍了publish函数的其中一种形式,在调用PubSubClient类的publish函数发布消息时,函数参数依次为要发布的主题和消息内容,不过publish函数还有第二种形式,该形式下需要传入的函数参数不仅有要发布的主题和消息内容,还有retainFlag配置参数,置为True则指定该消息为保留消息,置为False则指定该消息为非保留消息。
(2)使用ESP8266发布MQTT保留消息示例代码:
cpp
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
// 设置Wi-Fi接入信息
const char* ssid = "Zevalin_Computer";
const char* password = "00114514";
const char* mqttServer = "test.mosquitto.org"; //如无法使用,可更换为其它公用MQTT服务端地址
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);
int count; // Ticker计数用变量
void setup() {
Serial.begin(9600);
WiFi.mode(WIFI_STA); //设置ESP8266工作模式为无线终端模式
connectWifi(); //连接Wi-Fi
mqttClient.setServer(mqttServer, 1883); //设置MQTT服务端和端口号
connectMQTTServer(); //连接MQTT服务端
if (mqttClient.connected()) { // 如果开发板成功连接服务器
pubRetMQTTmsg(); // 发布信息
}
void loop() {
// 保持心跳
mqttClient.loop();
}
void connectMQTTServer(){
// 根据ESP8266的MAC地址生成客户端ID(避免与其它ESP8266的客户端ID重名)
String clientId = "esp8266-" + WiFi.macAddress();
// 连接MQTT服务端
if (mqttClient.connect(clientId.c_str())) {
Serial.println("MQTT Server Connected.");
Serial.println("Server Address: ");
Serial.println(mqttServer);
Serial.println("ClientId:");
Serial.println(clientId);
}
else {
Serial.print("MQTT Server Connect Failed. Client State:");
Serial.println(mqttClient.state());
delay(3000);
}
}
void pubRetMQTTmsg(){ // 发布信息
// 建立发布主题
String topicString = "Taichi-Maker-Ret-" + WiFi.macAddress();
char publishTopic[topicString.length() + 1];
strcpy(publishTopic, topicString.c_str());
// 建立即将发布的保留消息,消息内容为"Retained Msg"
String messageString = "Retained Msg";
char publishMsg[messageString.length() + 1];
strcpy(publishMsg, messageString.c_str());
// 实现ESP8266向主题发布retained信息
// 以下publish函数第三个参数用于设置保留信息(retained message)
if(mqttClient.publish(publishTopic, publishMsg, true)){
Serial.println("Publish Topic:");Serial.println(publishTopic);
Serial.println("Publish Retained message:");Serial.println(publishMsg);
}
else {
Serial.println("Message Publish Failed.");
}
}
void connectWifi(){ // ESP8266连接Wi-Fi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000); Serial.print(".");
}
Serial.println("");
Serial.println("WiFi Connected!");
Serial.println("");
}
(3)使用ESP8266删除MQTT保留消息示例代码,与使用ESP8266发布MQTT保留消息示例代码类似,修改pubRetMQTTmsg的函数实现即可。
cpp
void pubRetMQTTmsg(){ // 发布信息
// 建立发布主题
String topicString = "Taichi-Maker-Ret-" + WiFi.macAddress();
char publishTopic[topicString.length() + 1];
strcpy(publishTopic, topicString.c_str());
// 如果要删除主题的"保留消息",可以通过向该主题发布一条空的"保留消息"
String messageString = "";
char publishMsg[messageString.length() + 1];
strcpy(publishMsg, messageString.c_str());
// 实现ESP8266向主题发布retained信息
// 以下publish函数第三个参数用于设置保留信息(retained message)
if(mqttClient.publish(publishTopic, publishMsg, true)){
Serial.println("Publish Topic:");Serial.println(publishTopic);
Serial.println("Publish Retained message:");Serial.println(publishMsg);
}
else {
Serial.println("Message Publish Failed.");
}
}
十一、心跳机制
1、心跳机制的概念及作用
(1)心跳机制就是MQTT客户端在没有向MQTT服务端发送信息时,定时地向MQTT服务端发送一条消息,这条用于心跳机制的消息也被称作心跳请求(PINGREQ),心跳请求的作用正是用于告知MQTT服务端,当前MQTT客户端依然在线,MQTT服务端在收到MQTT客户端的心跳请求后,会回复一条消息,这条回复消息被称作心跳响应(PINGRESP)。

(2)由于心跳请求是MQTT客户端定时发送的,一旦MQTT服务端发现MQTT客户端停止发送请求信息,那么MQTT服务端就会知道,这台MQTT客户端已经断开了连接。
(3)这个心跳机制不仅可以用于MQTT服务端判断MQTT客户端是否保持连接,也可以用于MQTT客户端判断自己与MQTT服务端是否保持连接,如果MQTT客户端在发送心跳请求(PINGREQ)后,没有收到MQTT服务端的心跳响应(PINGRESP),那么MQTT客户端就会认为自己与MQTT服务端的连接已经被断开了。
2、心跳时间间隔的设置
(1)MQTT客户端连接服务端时会向MQTT服务端发送CONNECT报文,而CONNECT报文可以包含参数KeepAlive------心跳时间间隔,单位为s。

(2)如果CONNECT报文中将此参数明确,那么MQTT客户端和MQTT服务端都明确了心跳时间间隔,MQTT客户端知道多久要发送一条心跳请求给MQTT服务端,MQTT服务端知道多久没收到MQTT客户端的心跳请求算是连接断开。
3、心跳机制的运作
(1)如果MQTT客户端在心跳时间间隔内发布了消息给MQTT服务端,那么MQTT服务端不需要MQTT客户端发送心跳请求,也可以确定该MQTT客户端在线。

(2)如果MQTT客户端在心跳时间间隔内没有发布消息给MQTT服务端,那么MQTT客户端将主动发送一个心跳请求消息给MQTT服务端,以表面自己仍然在线。

(3)简而言之,MQTT客户端在心跳间隔时间内,如果有消息发布,那就直接发布消息而不发布心跳请求,但是在心跳间隔时间内,MQTT客户端没有消息发布,那么它就会发布一条心跳请求给MQTT服务端。
(4)在实际运行中,如果MQTT服务端没有在1.5倍心跳时间间隔内收到MQTT客户端发布的消息(PUBLISH)或心跳请求(PINGREQ),那么MQTT服务端就会认为这个MQTT客户端已经掉线。举例来说,如果心跳时间间隔是60秒,那么MQTT服务端在90秒内没有收到MQTT客户端发布的消息,也没有收到MQTT客户端的PINGREQ请求,那么它就会认为MQTT客户端已经掉线。
(5)心跳机制不仅仅用于MQTT服务端判断MQTT客户端是否在线,MQTT客户端也可以利用这一机制来判断自己是否与MQTT服务端仍保持连接,如果MQTT客户端发送了心跳请求(PINGREQ)给MQTT服务端一段时间后,没有收到MQTT服务端回复的心跳确认,那么MQTT客户端也会认为自己已经断开了与MQTT服务端的连接。