MQTT QoS的原理与应用
学习QoS的原理与应用
一、什么是服务质量?
MQTT服务质量(Quality of Service 缩写 QoS)正是用于告知物联网系统,哪些信息是重要信息需要准确无误的传输,而哪些信息不那么重要,即使丢失也没有问题。
MQTT协议有三种服务质量级别: QoS = 0 – 最多发一次 QoS = 1 – 最少发一次 QoS = 2 – 保证收一次 以上三种不同的服务质量级别意味着不同的MQTT传输流程。 对于较为重要的MQTT消息,我们通常会选择QoS>0的服务级别(即QoS 为1或2)。
QoS = 0 – 最多发一次 0是服务质量QoS的最低级别。当QoS为0级时,MQTT协议并不保证所有信息都能得以传输。也就是说,QoS=0的情况下,MQTT服务端和客户端不会对消息传输是否成功进行确认和检查。消息能否成功传输全看网络环境是否稳定。
也就是说,在QoS为0时。发送端一旦发送完消息后,就完成任务了。发送端不会检查发出的消息能否被正确接收到。
在网络环境稳定的情况下,信息传输一般是不会出现问题的。但是在环境不稳定的情况下,可能会在传输过程中出现MQTT消息丢失的情况。
QoS = 1 – 最少发一次
发送端将消息发送给接收端后,会等待接收端的确认。接收端成功接收消息后,会发送一条确认报文PUBACK给发送端。如果发送端收到了这条PUBACK确认报文,那么它就知道消息已经成功接收。
QoS = 2 – 保证收一次
MQTT服务质量最高级是2级,即QoS = 2。当MQTT服务质量为2级时,MQTT协议可以确保接收端只接收一次消息。 接收端收到QoS为2的消息后,会返回PUBREC报文作为应答。 发送端收到PUBREC报文后,会把此报文进行存储,并且返回PUBREL报文作为应答。 当接收端收到PUBREL报文后,会应答发送端一条PUBCOMP报文。至此,一次QoS2的MQTT消息传输就结束了。
2. 设置QoS
发布消息 客户端发布信息时,PUBLISH数据包中专有一个信息为qos。该信息正是用于设置客户端发布MQTT消息的QoS等级
订阅消息 同样的,在客户端订阅MQTT主题时,SUBSCRIBE数据包中也同样有一个信息用于设置订阅主题的QoS级别。客户端正是通过该主题来设置订阅主题的QoS级别的。
接收端连接服务端 另外,要想实现QoS>0的MQTT通讯,客户端在连接服务端时必须要将cleanSession设置为false。如果这一步没有实现,那么客户端是无法实现QoS>0的MQTT通讯。这一点非常关键,请您务必要留意。
3. 服务质量降级
对于发布和订阅消息的客户端,服务端会主动采用较低级别的QoS来实现消息传输。
4. 使用步骤
1.引入PubSubClient库
PubSubClient库目前只支持1级QoS订阅,因此我们将仅介绍如何使用ESP8266接收QoS=1的MQTT消息。
将QoS设置为1。 接收到服务端的信息代码如下(示例):
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
const char* ssid = "dajiating";
const char* password = "xxxx";
const char* mqttServer = "test.ranye-iot.net";
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);
const int subQoS = 1;
const bool cleanSession = false;
const char* willTopic = "willTopic";
const char* willMsg = "willMsg";
const int willQos = 0;
const int willRetain = false;
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
Serial.begin(9600);
WiFi.mode(WIFI_STA);
connectWifi();
mqttClient.setServer(mqttServer, 1883);
mqttClient.setCallback(receiveCallback);
connectMQTTserver();
}
void loop() {
if (!mqttClient.connected()) {
connectMQTTserver();
}
mqttClient.loop();
}
void connectMQTTserver(){
String clientId = "client-" + WiFi.macAddress();
if (mqttClient.connect(clientId.c_str(), NULL, NULL, willTopic, willQos, willRetain, willMsg, cleanSession)) {
Serial.print("MQTT Server Connected. ClientId: ");
Serial.println(clientId);
subscribeTopic();
} else {
Serial.print("MQTT Server Connect Failed. Client State:");
Serial.println(mqttClient.state());
delay(5000);
}
}
void receiveCallback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message Received [");
Serial.print(topic);
Serial.print("] ");
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println("");
Serial.print("Message Length(Bytes) ");
Serial.println(length);
if ((char)payload[0] == '1') {
digitalWrite(BUILTIN_LED, LOW);
} else {
digitalWrite(BUILTIN_LED, HIGH);
}
}
void subscribeTopic(){
String topicString = "Taichi-Maker-Sub-" + WiFi.macAddress();
char subTopic[topicString.length() + 1];
strcpy(subTopic, topicString.c_str());
if(mqttClient.subscribe(subTopic, subQoS)){
Serial.print("Subscribed Topic: ");
Serial.println(subTopic);
} else {
Serial.print("Subscribe Fail...");
}
}
void connectWifi(){
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi Connected!");
Serial.println("");
}
开发板服务质量为QoS1等级,不同的服务等级发送端在线,接收端,离线个在线接收结果如下表格
服务等级 | 0 | 1 | 2 |
---|
发送端 | 在线 | 在线 | 在线 | 接收端 | 断电不能接收 | 都可以接收 | 都可以接收 |
*都可以接收 :重新上线后也可以接收发送端在离线(断电)前发送的内容。
二、 保留消息
保留消息得到介绍
保留消息的作用 无论显示客户端在任何时间订阅室温主题,都会马上收到该主题中的“保留消息”,也就是客户端发布的最新室温消息。
发布保留消息的方法 MQTT设备发布的保留消息的流程与发布普通消息的流程十分类似。唯一区别是,在发布保留消息时,MQTT设备需要将PUBLISH报文中retainFlag设置为true(如上图所示)。
修改保留消息的方法 每一个主题只能有一个“保留消息”,如果客户端想要更新“保留消息”,就需要向该主题发送一条新的“保留消息”,这样服务端会将新的“保留消息”覆盖旧的“保留消息”。当有客户端订阅该主题时,服务端就会将最新的“保留消息”发送给订阅客户端了。
删除保留消息的方法 如果要删除主题的“保留消息”,可以通过向该主题发布一条空的“保留消息”,也就是发送一条0字节payload的“保留消息”
保留消息应用
示例1 – 发布保留消息
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
const char* ssid = "taichi-maker";
const char* password = "12345678";
const char* mqttServer = "test.ranye-iot.net";
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);
void setup() {
Serial.begin(9600);
WiFi.mode(WIFI_STA);
connectWifi();
mqttClient.setServer(mqttServer, 1883);
connectMQTTServer();
if (mqttClient.connected()) {
pubRetMQTTmsg();
}
}
void loop() {
mqttClient.loop();
}
void connectMQTTServer(){
String clientId = "esp8266-" + WiFi.macAddress();
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());
String messageString = "Retained Msg";
char publishMsg[messageString.length() + 1];
strcpy(publishMsg, messageString.c_str());
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(){
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi Connected!");
Serial.println("");
}
总结
QoS=1通讯时的注意事项 如想在MQTT通讯中实现服务质量等级为1级(QoS=1),我们要分别对消息的发布端课接收端进行相应的设置。以下列表中的内容是具体需要采取的措施。
接收端连接服务端时cleanSession设置为false 接收端订阅主题时QoS=1 发布端发布消息时QoS=1
QoS=2通讯时的注意事项 如想在MQTT通讯中实现服务质量等级为2级(QoS=2),我们要分别对消息的发布端和接收端进行相应的设置。以下列表中的内容是具体需要采取的措施。
接收端连接服务端时cleanSession设置为false 接收端订阅主题时QoS=2 发布端发布消息时QoS=2
PubSubClient 库只能使用QoS 1 级 来订阅消息。 QoS 1 开发板断电后,上线依然可以接收消息 QoS 1 发送和接受有确认请求,来确保消息接受
//MQTT使用保留消息的使用 retainFlag设置为true
const int subQoS = 1;
const bool cleanSession = false;
mqttClient.connect(clientId.c_str(), NULL, NULL, willTopic, willQos, willRetain, willMsg, cleanSession)
mqttClient.subscribe(subTopic, subQoS);
mqttClient.publish(publishTopic, publishMsg, true)
|