1.簡介
1.1組織MQTT
這份文檔將分成以下七個部分來組織:
l 簡介
l MQTT 控制包的格式
l MQTT 控制包
l 操作行爲
l 安全
l 使用 WebSocket 作爲網絡傳輸
l 目標的一致性
1.2術語
網絡連接
爲MQTT 傳輸協議提供底層構建:
l 它連接着客戶端和服務端
l 它提供雙工發送有序、無損、字節流的能力
詳情請移步 4.2
1.2.1客戶端:
一個使用 MQTT 的程序或設備。一個客戶端總是建立一個網絡連接到服務端。它可以:
l 發佈其他客戶端可能感興趣的應用消息
l 只接受訂閱的應用消息
l 退訂放棄接收應用消息
l 向服務器發起斷開連接
1.2.2服務端:
一個介於客戶端之間的信息中轉器,連接客戶端之間的訂閱。一個服務端:
l 接受來自客戶端的網絡連接
l 接受由客戶端發佈的應用消息
l 處理來自客戶端訂閱和退訂請求
l 轉發應用消息到匹配的客戶端
1.2.3訂閱:
一個訂閱由一個主題過慮器和一個最大的QoS (服務質量)組成。一個訂閱只能關聯一個會話。
一個會話可以包含多個訂閱。一個會話內的所有訂閱必須有唯一的主題過濾器。
1.2.4主題名字:
作爲應用信息的標誌,匹配服務端上的訂閱。服務端發送一個應用消息的副本到匹配的客戶端。
1.2.5主題過濾器:
一個包含在一個訂閱中的表達式,去匹配感興趣一個或者多個主題。一個主題過濾器可以包含通配符。
1.2.6會話:
客戶端和服務端之間的一個有狀態的交互信息體。一些會話只活躍在一個網絡連接,有一些可以跨越多個連續的網絡連接,在客戶端和服務端之間。
1.2.7MQTT 控制包:
一個包的信息是通過網絡連接來承載。在MQTT 的規範文檔裏定義了14種不同類型的控制包,其中一個(PUBLISH 包)被用來發送應用消息。
1.3規範文檔引用
1.4非規範文檔引用
1.5數據闡述
1.5.1位碼
在一個字節裏使用7到0表示。7是最高的有效位,0則是最低的有效位。
1.5.2整形數據值
整形數據值是使用大端的編碼16位表示:既高位字節在低位字節的前面。這意味着一個16位的消息在網絡傳輸時,最高有效字節(MSB)會被先發送,然後是最低有效字節(LSB)。
1.5.3UTF-8 編碼的字符串
在控制包裏的文本字段會使用UTF-8進行編碼。UTF-8是一種基於 Unicode 字符的高效編碼,在基於文本的通信中,優於基於ASCII 字符的編碼。
在每個待編碼的字符串前面追加兩個字節作爲它編碼後的長度,如下下圖所示。因此,單個編碼的字段編碼長度是有限制的,最大可達65535 字節。
字節 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
字節1 |
字符串長度 MSB |
|||||||
字節 2 |
字符串長度 LSB |
|||||||
字節 3 … |
UTF-8編碼的字符數據(如果長度 > 0) |
UTF-8 編碼的字符數據一定是符合Unicode 規範文檔和RFC 定義的格式。尤其不能包含 U+D800 – U+DFFF之間的碼值。如果服務端或客戶端接受到一個含有不規範的UTF-8 編碼的數據包,它會關閉網絡連接[MQTT-1.5.3-1]。
一個UTF-8 編碼的字符串一定不能包含一個null(U+0000)編碼的字符。如果一個接受者(客戶端/服務端) 接收一個包含U+0000 的控制包,它將會關閉網絡連接[MQTT-1.5.3-2]。
數據裏不建議包含下面列出的Unicode 編碼。如果一個接收者(客戶端/服務端) 接收到一個包含列表中的任何一個,它可能會關閉網絡連接。
l U+0001 – U+001F 控制字符
l U+007F – U+009F 控制字符
在Unicode 編碼規範裏,有一些編碼是非字符(如:U+0FFFF)
一個UTF-8 編碼的序列 0xEF、0xBB、0xBF 總是會被解碼成 U+FEFF(“零寬度非換行空格”),當它出現在一個字符串中時,數據包接收者不能跳過或過濾掉它[MQTT-1.5.3-3]。
1.5.3.1一個不規範例子
例如,字符串“A□”是一個大寫的拉丁字母“A”後跟着一個U+2A6D4 碼值(它代表一個CJK象形文字擴展字符“B”) 將會被編譯成如下:
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|
字節 1 |
字符串長度MSB (0x00) |
|||||||
|
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
字節2 |
字符串長度 LSB(0x05) |
|||||||
|
0 |
0 |
0 |
0 |
0 |
1 |
0 |
1 |
字節3 |
‘A’ (0x41) |
|||||||
|
0 |
1 |
0 |
0 |
0 |
0 |
0 |
1 |
字節4 |
(0xF0) |
|||||||
|
1 |
1 |
1 |
1 |
0 |
0 |
0 |
0 |
字節5 |
(0xAA) |
|||||||
|
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
字節6 |
(0x9B) |
|||||||
|
1 |
0 |
0 |
1 |
1 |
0 |
1 |
1 |
字節7 |
(0x94) |
|||||||
|
1 |
0 |
0 |
1 |
0 |
1 |
0 |
0 |
1.6排版約定
在這份文檔裏使用黃色突顯統一聲明,每一聲明都會分配一個[MQTT-x.x.x-y]格式的引用。
2.MQTT 控制包格式
2.1MQTT 控制包的結構
MQTT 協議通過交換一些列的 MQTT 控制包進行工作。在這個章節裏將帶你認識這些包的格式。
一個 MQTT 控制包有下面的三個部分組成,它總是以下面表格所示的順序進行排列。
固定頭,所有的 MQTT 控制包都有 |
可變頭,一些 MQTT 控制包纔有 |
負載,一些 MQTT 控制包纔有 |
2.2固定頭
每一個 MQTT 控制包都包含一個固定頭。格式如下:
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|
字節 1 |
MQTT 控制包類型 |
特定的標誌位 |
||||||
字節 2…. |
剩餘的長度 |
2.2.1MQTT 控制包類型
位置:字節 1 [7-4]位
用4位的無符號值表示,值如下所示:
名字 |
值 |
流轉方向 |
描述 |
保留 |
0 |
禁止 |
保留 |
CONNECT |
1 |
客戶端到服務端 |
客戶端請求連接到服務端 |
CONNACK |
2 |
服務端到客戶端 |
連接確認 |
PUBLISH |
3 |
客戶端到服務單 或 服務端到客戶端 |
發佈消息 |
PUBACK |
4 |
客戶端到服務單 或 服務端到客戶端 |
發佈確認 |
PUBREC |
5 |
客戶端到服務單 或 服務端到客戶端 |
發佈收到(有保證的交付第一部分)received |
PUBREL |
6 |
客戶端到服務單 或 服務端到客戶端 |
發佈釋放(有保證的交付第二部分)release |
PUBCOMP |
7 |
客戶端到服務單 或 服務端到客戶端 |
發佈完成(有保證的交付第三部分)complete |
SUBSCRIBE |
8 |
客戶端到服務端 |
客戶端訂閱請求 |
SUBACK |
9 |
服務端到客戶端 |
訂閱確認 |
UNSUBSCRIBE |
10 |
客戶端到服務端 |
客戶端退訂請求 |
UNSUBACK |
11 |
服務端到客戶端 |
退訂確認 |
PINGREQ |
12 |
客戶端到服務端 |
ping請求 |
PINGRESP |
13 |
服務端到客戶端 |
ping響應 |
DISCONNECT |
14 |
客戶端到服務端 |
客戶端斷開 |
Reserved |
15 |
禁止 |
保留 |
2.2.2標誌位(Flags)
在固定頭裏的第一個字節裏剩餘的位[3-0],包含着每個不同類型的MQTT 控制包的對應的標誌位值,正如下列出表格所示。在下列表中,當一個標誌位被標記爲“保留”時,這表明它留作將來使用,所以一定不能爲這些位設置值[MQTT-2.2.2-1]。如果接收者接收到非法的標記爲值,它會主動關閉網絡連接[MQTT-2.2.2-2]。想了關於處理此錯誤的更多細節,請移步到4.8小節。
控制包 |
固定頭標誌 |
3 |
2 |
1 |
0 |
CONNECT |
保留 |
0 |
0 |
0 |
0 |
CONNACK |
保留 |
0 |
0 |
0 |
0 |
PUBLISH |
MQTT3.1.1中使用 |
DUP1 |
QoS2 |
QoS1 |
RETAIN3 |
PUBACK |
保留 |
0 |
0 |
0 |
0 |
PUBREC |
保留 |
0 |
0 |
0 |
0 |
PUBREL |
保留 |
0 |
0 |
1 |
0 |
PUBCOMP |
保留 |
0 |
0 |
0 |
0 |
SUBSCRIBE |
保留 |
0 |
0 |
1 |
0 |
SUBACK |
保留 |
0 |
0 |
0 |
0 |
UNSUBSCRIBE |
保留 |
0 |
0 |
1 |
0 |
UNSUBACK |
保留 |
0 |
0 |
0 |
0 |
PINGREQ |
保留 |
0 |
0 |
0 |
0 |
PINGRESP |
保留 |
0 |
0 |
0 |
0 |
DISCONNECT |
保留 |
0 |
0 |
0 |
0 |
DUP1 = 重複分發一個“PUBLISH”控制包
QoS2 = “PUBLISH”服務的質量(QoS)
RETAIN3 =“PUBLISH”剩餘標誌
關於PUBLISH裏的DUP,QOS,RETAIN標誌位的更多細節,請查閱3.3.1 小節中關於它們的描述。
2.2.3剩餘的長度(Remaining Length)
位置:從第二個字節開始
“剩餘的長度”是指當前包剩餘的字節數,包括了可變頭和負載。但是不包含用於編碼“剩餘的長度”的字節。
剩餘長度使用可變長度的編碼方式進行編碼,單個字節的值可達127。超過的值採用下面方式處理。每個字節最低7位用於數據的編碼,最高有效位用於表示後續字節。因此,每個字節可以表示128個值和一個“延續位”。剩餘長度字段最大可以用4個字節表示。
[作者注]
上面講的定義過於抽象,很難理解啓動的含義。這裏舉一個例子說明一下。例如1,這裏有一個十進制的數字“64”,因爲其值小於127,所以用一個字節就可以表示出來:
十進制:64
十六進制:0x40
二進制:0100 0000
例子2,當這個數字是321時,應該如何編碼呢?在做之前,我們可以先拆分一下這個數字爲65 + (2 * 128)。從上面可以看出,這個數字可以用兩個字節進行編碼。根據算法,可以得出第一個字節的值爲193(65 + 128),第二個字節的值爲2。
十進制:[193] [2]
十六進制:[0xC1] [0x02]
從上面的結果可以看到,第二個字節的值爲(第1字節值 - 65) / 128。
非規範註釋
這允許應用程序發送高達268435455(256MB) 的控制包。在信道中可以用0xFF,0xFF,0xFF,0x7F表示。下面列出剩餘長度值和編碼字節對應關係:
位碼 |
從(From) |
到(TO) |
1 |
0(0x00) |
127(0x7F) |
2 |
128(0x80, 0x01) |
16383(0xFF, 0x7F) |
3 |
16384(0x80, 0x80, 0x01) |
2097151(0xFF, 0xFF, 0x7F) |
4 |
2097152(0x80, 0x80, 0x80, 0x01) |
268435455(0xFF, 0xFF, 0xFF, 0x7F) |
非規範註釋
2.2.3.1編碼算法(JAVA)
編譯的值範圍:[0-268435455]
2.2.3.2解碼算法(JAVA)
2.3可變頭
某些類型的MQTT 控制包會包含一個可變頭,介於固定頭和負載之間。可變頭的內容取決於包的類型。但是,可變頭中的標識符字,在幾種包類型裏是段通用的。
2.3.1包標識符
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|
字節 1 |
包標識符 MSB |
|||||||
字節 2 |
包標識符 LSB |
很多類型控制包的可變頭組件都包含2個字節的包標識符。這些控制包是 PUBLISH(QoS > 0), PUBACK, PUBREC, PUBREL, PUBCOMP, SUBSCRIBE, SUBACK, UNSUBSCRIBE, UNSUBACK。
SUBSCRIBE, UNSUBSCRIBE 和 PUBLISH(QoS > 0) 控制包必須包含一個非零的16位包標識符[MQTT-2.3.1-1]。客戶端每次發送其類型中一個新包時,必須給它分配一個當前沒有使用過的包標識符[MQTT-2.3.1-2]。如果客戶端想重發個別控制包時,那麼它必須使用和之前相同的包標識符。當客服端處理完對應的請求確認包後,這個包標識符就可以重用。例如,在PUBLISH,當QoS =1對應確認包爲PUBACK,當QoS=2對應爲PUBCOMP。至於SUBSCRIBE 或 UNSUBSCRIBE 對應爲 SUBACK,UNSUBACK[MQTT-2.3.1-3]。在相同條件下,服務端發送一個QoS > 0 的 PUBLISH 時,上訴規則也成立[MQTT-2.3.1-4]。
如果一個 PUBLISH 包的QoS的值設置爲0時,它一定不會包含一個包標識符[MQTT-2.3.1-5]。
一個 PUBACK, PUBREC, PUBREL 包中的包標識符一定要與源 PUBLISH 相同[MQTT-2.3.1-6]。同理,SUBACK 和 UNSUBACK 與之對應的 SUBSCRIBE 和 UNSUBSCRIBE 一致[MQTT-2.3.1-7]。
下面表格列出控制包包含包標識符:
類型 |
包標識符字段 |
CONNECT |
NO |
CONNACK |
NO |
PUBLISH |
YES(QoS > 0) |
PUBACK |
YES |
PUBREC |
YES |
PUBREL |
YES |
PUBCOMP |
YES |
SUBSCRIBE |
YES |
SUBACK |
|
UNSUBSCRIBE |
YES |
UNSUBACK |
YES |
PINGREQ |
NO |
PINGRESP |
NO |
DISCONNECT |
NO |
客戶端和服務端可以獨立分配自己的包標識符。因此,它們可以使用相同的包標識符參與併發的信息通信。
非規範註釋
一個客戶端發送一個PUBLSH 包含0x1234 包標識符給服務端,它在收到服務器回覆的一個包含相同標識符但是不一樣的PUBLISH 包之前,會先收到一個 PUBACK。
Client Server
PUBLISH Packet Identifier=0x1234---à
ß--PUBLISH Packet Identifier=0x1234
PUBACK Packet Identifier=0x1234---à
ß--PUBACK Packet Identifier=0x123
2.4負載
一些 MQTT 控制包會包含一個負載作爲最後一部分,在第3章會有其更多的細節。在PUBLISH 包中,它代表着一個應用信息。如下表格列出不同類型的控制包的負載:
控制包 |
負載 |
CONNECT |
Required(必須) |
CONNACK |
None(沒有) |
PUBLISH |
Option(可選) |
PUBACK |
None(沒有) |
PUBREC |
None(沒有) |
PUBREL |
None(沒有) |
PUBCOMP |
None(沒有) |
SUBSCRIBE |
Required(必須) |
SUBACK |
Required(必須) |
UNSUBSCRIBE |
Required(必須) |
UNSUBACK |
None(沒有) |
PINGREQ |
None(沒有) |
PINGRESP |
None(沒有) |
DISCONNECT |
None(沒有) |
3.MQTT 控制包
3.1CONNECT – 客戶端向服務端發起一個連接請求
當一個客戶端到服務端之間的網絡連接建立完成後,客戶端向服務端發送的第一個包必須是 CONNECT[MQTT-3.1.0-1]。
一個客戶端在整個網絡連接生命週期只能夠發送一次 CONNECT 包。服務端必須處理從客戶端第二次發過的違規 CONNECT 包,並斷開與客戶端的連接[MQTT-3.1.0-2]。查看4.8小節瞭解處理此錯誤的更多信息。
負載可以包含一個或多個編碼字段。它們爲客戶端指定一個唯一的標識符,一個Will topic,Will message,用戶名和密碼。但是,客戶端標識符是可選的,它的出現以否有可變頭中的標誌位設置有關。
3.1.1固定頭
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|||||
字節 1 |
MQTT 控制包類型(1) |
保留 |
|||||||||||
|
0 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
|||||
字節 2…. |
剩餘的長度 |
剩餘長度字段
剩餘長度等於可變頭長度(10字節)加上負載的長度。它的編碼方式請參考2.2.3小節。
3.1.2可變頭
CONNECT 包的可變頭有四部分組成:協議名稱、協議級別、連接標記和Keep Alive(注意順序)。
3.1.2.1協議名稱
協議名稱是一個用 UTF-8 編碼的字符串,代表協議名稱“MQTT”。這個字符串,它的偏移量和長度不會隨着 MQTTT 規範版本的變遷而改變。
如果協議名稱不正確,服務端可能會會關閉連接,或者按照其他的方式繼續處理 CONNECT 包。在稍後的例子裏,服務端採用是第一種方案,不會繼續處理 CONNECT 包[MQTT-3.1.2-1]。
非規範的註釋
包檢測器,如防火牆,會使用協議名稱來識別 MQTT 信息流。
3.1.2.2協議級別
|
描述 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
協議級別 |
|||||||||
byte 7 |
級別(4) |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
客戶端使用8位無符號值表示協議的修訂級別。3.1.1版本的協議級別用4(0x04)來表示。如果從客戶端發出的 CONNECT 包中包含一個服務端不支持的協議級別,服務端會向客戶端返回包含 0x01(不接受的協議級別)的CONNACK 包,並關閉連接[MQTT-3.1.2-2]。
3.1.2.3標誌位
連接標誌字節中包含了用於設置 MQTT 連接行爲的若干參數。它決定這些字段否是可以在負載中出現。
連接標誌位列表:
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|
用戶名 |
密碼 |
Will Retain |
Will QoS |
Will Flag |
Clean Session |
保留 |
|
byte 8 |
× |
× |
× |
× |
× |
× |
× |
0 |
服務端必須要校驗 CONNECT 控制包裏的保留位值是否爲 0,如果不爲0,關閉連接[MQTT-3.1.2-3]。
3.1.2.4Clean Session
位置:連接標誌字節的第1位
這個標誌位指定處理會話狀態(session state)的方式.
客戶端和服務端可以保存會話的狀態,保證消息傳輸的可靠性(斷線重連期間,消息不丟失)。這個標誌用來控制會話的生命週期。
如果 Clean Session 被設置爲0,服務端可以基於會話的狀態(基於客戶端的標識符)恢復之前與客戶端之間的通信會話。如果沒有會話和客戶標識符關聯,服務端必須創建一個新的會話與之關聯。當客戶端和服務端斷開連接後,雙方要保存會話[MQTT-3.1.2-4]。當一個Clean Session位置爲0的會話斷連後,服務端必須繼續保存客戶關注過且QoS值爲1和2的消息,這些都是會話狀態的一部分[MQTT-3.1.2-5]。
它也可以使用相同的策略保存 QoS值爲0的消息。
如果 Clean Session 被設置爲1,客戶端和服務端將丟棄之前的會話並創建信息的會話。這個會話的生命週期等同這個網絡連接。狀態數據將不會與後續會話共享[MQTT-3.1.2-6]。
客戶端的會話狀態包含:
l 從服務端接收到,但尚未確認的 QoS爲1和2消息
服務端的會話狀態包含:
l 一個會話實例,即使其狀態爲空
l 客戶端的訂閱
l 發送到客戶端,但尚未確認的QoS爲1和2消息
l 從客戶端接收到,但尚未確認的 QoS爲1和2消息
l 可選,待發送到客戶端 QoS爲0的消息
在服務端,信息的保存不屬於會話狀態的組成部分,它們不會因爲會話的實效而被刪除[MQTT-3.1.2.7]。
查看4.1 小節獲取更多關於存儲狀態的限制和細節。
當 clean session 被設置爲1時,客戶端和服務度不需要處理的會話狀態的刪除。
非規範註釋
在發生故障時,確保狀態的一致性,這樣,客戶端可以複製它的狀態,設置clean session 的值爲1去連接服務端,直到它能連接成功爲止。
非規範註釋
一般,一個客戶端會一直使用相同的 clean session(1/0) 設置去連接,很少會交替的使用。但是,如何選擇,這個有你具體的應用需求決定。但你需要知道的是,當一個客戶端使用了clean session 設置爲1將會丟失舊的的應用消息(斷連期間),並且在每次重連後需要重新訂閱需要關注的主題。反之,當它設置爲0時,不會丟失QoS爲1和2的消息(斷連期間)。因此,假如你對消息連續性要求非常嚴格,可以使用 QoS爲1或2,clean session 爲0的設置。
非規範註釋
當一個客戶端通過clean session爲0連接時,在斷連後,服務端會維護它的MQTT會話狀態,當在某個時間點它們再次重連時,建議也是使用相同的 clean session設置。假如,確實不再需要該會話時,可以在最後一次會話中使用 clean session爲1進行連接,且在使用完後關閉它。
3.1.2.5Will Flag(will 標記)
位置:連接標誌字節中的第2位
如果 will標誌被設置爲1,且連接請求被接受時,服務端會保存一個與網絡連接關聯的will 消息。當這個連接被關閉時,服務端一定會將它發佈出去,除非再次之前服務端接收到一個 DISCONNECT 包(會觸發刪除will消息)[MQTT-3.1.2-8]。
在下列場景會觸發will 消息發送,但不限於:
l I/O 錯誤或服務端檢測網絡連接失敗
l 客戶端在 keep alive 週期內無法正常通信
l 沒有發送 DISCONNECT 的客戶端關閉操作
l 協議錯誤導致的服務端關閉連接
如果 will 標記設置爲1,服務端將會使用連接標記字節中的 will QoS 和 will retain,且will 主題(topic) 和 will 消息必須在負載中出現[MQTT-3.1.2-9]。
服務端一旦發佈完成(will 消息)或接收到一個 DISCONNECT 包時,will 消息必將會被從會話狀態中移除[MQTT-3.1.2-10]。
如果 will 標記設置爲0時,連接標記字節中的will QoS 和 will Retain 字段必須設置爲0,且負載中不能出現will 主題(topic) 和 will 消息[MQTT-3.1.2-11。
如果will 標記設置0,當網絡連接結束時,服務度不會發佈一個will 消息[MQTT-3.1.2-12]。
服務端應當儘快的發佈will 消息。但是,當服務器關閉或故障時,will 消息發佈可能會被推遲到隨後服務器重啓之後。
3.1.2.6Will QoS
位置:連接標記字節中第3和 4位
這兩個位指定將要發佈 will 消息的QoS級別。
如果 will 標記設置爲0,那麼 will QoS 必須設置爲0(0x00)[MQTT-3.1.2-13]。
如果 will 標記設置爲1,will Qos 可以設置爲0(0x000)、1(0x01)、2(0x02)。但是它不能設置爲3(0x03)[MQTT-3.1.2-14]。
3.1.2.7Will Retain
位置:連接標記字節中第5位
這個位控制,當will 消息被髮布時,是否保存will 消息。
如果will標記設置爲0,那麼will Retain 必須設置爲0[MQTT-3.1.2-15]。
如果will 標記設置爲1:
l 如果will Retain設置爲0,服務端必須發佈一個非存儲的will消息[MQTT-3.1.2-16]。
l 如果will Retain設置爲1,服務端必須發佈一個可存儲的will消息[MQTT-3.1.2-17]。
3.1.2.8密碼(Password)標記
位置:連接標記字節中第6位
如果密碼標記設置爲0,密碼不允許出現在負載裏[MQTT-3.1.2-20]。
如果密碼標記設置爲1,密碼必須出現在負載裏[MQTT-3.1.2-21]。
如果用戶名標記設置爲0,密碼標記必須設置爲0[MQTT-3.1.2-22]。
3.1.2.9用戶名(User Name) 標記
位置:連接標記字節中第7位
如果用戶名標記設置爲0,用戶名不允許出現在負載中[MQTT-3.1.2-18]。
如果用戶名標記設置爲1,用戶名必須出現在負載中[MQTT-3.1.2-19]。
3.1.2.10Keep Alive
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte 9 |
Keep Alive MSB |
|||||||
byte 10 |
Keep Alive LSB |
Keep Alive 以秒爲單位。使用一個16位的數值表示,它表示客戶端最大空閒時間。它的職責是保證客戶端發送控制包的空閒(間隔)時間不能超過 Keep Alive的值。當客戶端長時間沒有控制包需要發送時,必須發送一個 PINGREQ包[MQTT-3.1.2-23]。
客戶端隨時都可以發送 PINGREQ包,當服務端和網絡連接正常的時,會回覆一個 PINGRESP 包給客戶端。
如果 Keep Alive 設置爲一個非0值,且服務端在1.5倍的Keep Alive時間週期內沒有收到任何一個從客戶端發過來的控制包,它將會斷開與客戶端之間網絡連接[MQTT-3.1.2-24]。
當客戶端發送一個 PINGREQ包後,如果,其在一個有效的時間內沒有收到服務端回覆的PINGRESP包,它應該關閉和服務端之間的網絡連接。
當Keep Alive設置爲0時,相當於關閉keep alive(保活)機制。意思是,在這用場景下,關閉非活動的客戶端連接就不是必須的了。
注意:服務端可以關閉一個非活動或已經沒有響應的客戶端,不管客戶端設置的Keep Alive值。
非規範註釋
Keep Alive值得設置根據具體的應用場景需要;一般設置幾分鐘。最大值是18個小時15分15秒。
3.1.2.11非規範的可變頭完整案例
|
描述 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
協議名稱 |
|||||||||
byte 1 |
Length MSB(0) |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
byte 2 |
Length LSB(4) |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
byte 3 |
‘M’ |
0 |
1 |
0 |
0 |
1 |
1 |
0 |
1 |
byte 4 |
‘Q’ |
0 |
1 |
0 |
1 |
0 |
0 |
0 |
1 |
byte 5 |
‘T’ |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
0 |
byte 6 |
‘T’ |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
0 |
協議級別 |
|||||||||
|
描述 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte7 |
級別(4) |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
連接標記字節 |
|||||||||
byte 8 |
描述 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
用戶名 |
密碼 |
Will Retain |
Will QoS |
Will Flag |
Clean Session |
保留 |
|||
1 |
1 |
0 |
0 |
1 |
1 |
1 |
0 |
||
Keep Alive |
|||||||||
Keep Alive MSB(0) |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
|
byte 10 |
Keep Alive LSB(10) |
0 |
0 |
0 |
0 |
1 |
0 |
1 |
0 |
3.1.3負載(Payload)
CONNECT 包的負載包含一個或多個帶長度前綴的域(fields),具體包含什麼由可變頭中的標記位決定。如果存在,必須按照下列的順序進行排列,will主題(topic),will消息,用戶名,密碼[MQTT-3.1.3-1]。
3.1.3.1客戶端標識符(Client Identifier)
每個連接到服務端的客戶端唯一標識(客戶端Id)。客戶端Id通常會被客戶端和服務端用於關聯會話狀態[MQTT-3.1.3-2]。
客戶端Id必須放在CONNECT包負載中的第一個域中[MQTT-3.1.3-3]。
客戶端Id必須是UTF-8編碼的字符串,關於UTF-8編碼請參考文檔中1.5.3小節的內容[MQTT-3.1.3-4]。
服務端允許客戶端Id的UTF-8編碼字節長度爲1到23,且只能包含以下字符,“0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ”[MQTT-3.1.3-5]。
服務端可以允許客戶度Id的編碼長度超過23。服務端也允許客戶端Id包含超出上面列出字符。
服務端可以允許客戶設置一個0字節長度的客戶Id,如果這樣做了,服務端必須把這樣情況當做特殊情況來處理,並且分配一個唯一的客戶端Id給客戶端。然後,如正常的CONNECT包(帶有效的客戶端Id)一樣處理它[MQTT-3.1.3-6]。
如果客戶端分配了一個0字節的客戶端Id,客戶端必須同時將clean session設置爲1[MQTT-3.1.3-7]。
如果客戶端分配了一個0字節的客戶端Id,並將clean session設置爲0,服務端必將回復一個包含0x02(非法的標識符)返回碼的CONNACK包,並關閉網絡連接[MQTT-3.1.3-8]。
如果服務端拒絕了客戶端Id,它必須要返回一個包含0x02返回碼的CONNACK的響應包[MQTT-3.1.3-9]。
非規範的註釋
一個客戶端實現要提供一個便利的方法生成一個隨機的客戶端Id。當clean session設置0時,推薦使用這種方法。
3.1.3.2Will 主題(Will Topic)
如果will標記設置爲1,will主題將是負載中的下一個域(字段)。Will主題必須是一個UTF-8編碼的字符串,關於UTF-8編碼請參考文檔中的1.5.3小節[MQTT-3.1.3-10]。
3.1.3.3Will 消息(Will Message)
如果will標記設置爲1 ,will消息將是負載中的下一個域(字段)。Will消息定義了應用消息,服務端將會如3.1.2.5小節中描述那樣將它發佈到will主題上。這個域有2個字節長度組成,緊跟是0或多個字節will消息的內容序列。跟隨的數據的字節長度不含前面的那2個字節。當will消息被髮布到will主題時,僅僅是will消息內容部分,而不包括開始的兩個字節。
3.1.3.4用戶名
如果用戶名標記設置爲1,它將是負載的下一個域。用戶名必須是一個UTF-8編碼的字符串[MQTT-3.1.3-11]。它可以被服務端用作認證和授權。
3.1.3.5密碼
如果密碼標記設置爲1,它將是負載的下一個域。密碼域包含一個2字節前綴的長度字段和0到65535個字節的二進制數據(它不包含表示長度本身的兩個字節)。
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte 1 |
Keep Alive MSB |
|||||||
byte 2 |
Keep Alive LSB |
|||||||
byte 3 … |
數據(length > 0) |
3.1.4響應
注意:在同一個TCP端口或其他的網絡節點上,服務器可以支持多個協議(包括早期的協議版本)。如果服務器的協議版本是MQTT 3.1.1,它會做如下的校驗:
1.如果在網絡連接創建成功後,服務端在有效的時間間隔內沒有接收到一個CONNECT 包,服務端應該關閉這個連接。
2.服務端必須校驗CONNECT包是否符合規範(如3.1小節所訴)。如果不合法,關閉網絡連接(不回覆CONNACK 包)[MQTT-3.14-1]。
3.服務端可以檢查CONNECT包中的內容以滿足更多的約束,可以執行認證和授權檢查。如果不通過,它應該返回一個合適的帶非0返回碼的CONNACK包,並關閉連接。更多的細節請查看3.2小節。
如果校驗成功,服務端執行下面的步驟:
1.如果客戶端Id關聯的客戶端已經連接到服務端,那麼,服務端必須關閉此客戶端[MQTT-3.14-2]。
2.服務端必須執行clean session的處理(如3.1.2.4所描述)[MQTT-3.1.4-3]。
3.服務端必須回覆一個帶非0返回值的CONNACK包對CONNECT包進行確認[MQTT-3.14-4]。
4.可以消息分發和keep alive 監聽
客戶端發送完一個CONNECT包之後,可以馬上發送其他的控制包;客戶端不需要等待服務端的CONNACK
如果服務端拒絕CONNECT,它不會處理CONNECT包之後發送的任何數據[MQTT-3.1.4-5]。
非規範註釋
客戶端通常會等待一個CONNACK包,然而,如果客戶端利用這個空檔繼續向服務端發送控制包,這將簡化客戶端的實現,因爲它不必關心連接的狀態。在收到服務端返回拒絕CONNACK包之前,客戶端可以接受任何的數據。
3.2CONNACK – 確認連接請求
CONNACK包是服務端接收到客戶端發送的CONNECT包後的響應。第一個從服務端發送客戶端的包必須是CONNACK包[MQTT-3.2.0-1]。
如果客戶端在合理的時間裏,沒有接收到從服務端返回的CONNACK包,客戶端應該關閉這個連接。這個“合理”時間對不同的應用類型和通信的基礎設施要不同的要求。
3.2.1固定頭
數據格式:
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte 1 |
MQTT控制包類型(2) |
保留 |
||||||
|
0 |
0 |
1 |
0 |
0 |
0 |
0 |
0 |
byte 2 |
剩餘長度(2) |
|||||||
|
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
剩餘長度字段
這是可變頭的長度,CONNACK包的長度爲2。
3.2.2可變頭
數據格:
|
描述 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
連接確認標記 |
保留 |
SP1 |
|||||||
byte 1 |
|
0 |
0 |
0 |
0 |
1 |
0 |
0 |
× |
連接返回碼 |
|||||||||
byte 2 |
|
× |
× |
× |
× |
× |
× |
× |
× |
3.2.2.1連接確認標記(Connect Acknowledge Flags)
“連接確認標記”用一個字節來表示。7-1位是保留位,必須設置爲0。0位(SP1)是當前會話(session present)標記。
3.2.2.2當前會話(Session Present)
位置:連接確認標記字節第0位
如果服務端接受一個clean session爲1的連接,服務端必須返回一個session present和返回碼都爲0的CONNACK確認包[MQTT-3.2.2-1]。
如果服務端接受了一個clean session爲0的連接,session present的值取決於服務端是否已經保存與客戶端Id關聯的會話狀態。如果服務端已經保存,它必須把CONNACK裏的session present設置爲1[MQTT-3.2.2-2]。反之,必須設置爲0。除此之外,還要設置一個爲0的返回碼[MQTT-3.2.2-3]。
Session present 能讓客戶端和服務端建立一個狀態共享的視圖,不管這時是否有會話狀態的存在。
一旦一個會話初始化完成後,客戶端和會話狀態期望服務端可以維護這個會話狀態。如果從客戶端接受到的session present不是服務端所預期的值時,客戶端可以選擇是否繼續使用當前會話,也可以關閉它。客戶端可以丟棄客戶端和服務端的會話狀態,先斷開連接,使用clean session爲1重連,之後再斷開連接。
如果一個服務端發送一個了一個包含非0的返回碼CONNACK包,那麼必須將它的session present設置爲0[MQTT-3.2.2-4]。
3.2.2.3連接返回碼
可變頭的第2個字節。
連接返回碼的值用一個無符號字節表示。如果服務端接收到一個無法處理的(不是包格式問題,某些業務上或是安全上的限制,如認證等)CONNECT包時,那麼,它將會返回一個合適的、包含非0的連接返回碼的CONNACK包。如果服務端發送完了上訴格式的包後,它必須關閉連接[MQTT-3.2.2-5]。下面列表列出了連接返回碼的值:
值 |
響應返回碼 |
描述 |
0 |
0x00 |
連接被接受 |
1 |
0x01 |
連接拒絕,服務端不支持的MQTT協議級別 |
2 |
0x02 |
連接拒絕,服務端不允許的客戶端Id |
3 |
0x03 |
連接拒絕,網絡連接已經建立,但是服務端不可用 |
4 |
0x04 |
連接拒絕,數據中的用戶名和密碼有缺陷 |
5 |
0x05 |
連接拒絕,客戶端沒有連接的權限 |
6-255 |
× |
保留 |
如果沒有在上表列出的連接返回值默認是可以用的,除此之外,服務端將會關閉網絡連接,且不會發送CONNAC返回包[MQTT-3.2.2-6]。
3.2.3負載
CONNACK包沒有負載。
3.3PUBLISH – 發佈消息
PUBLISH控制包用於客戶端和服務端之間的應用消息的傳輸。
3.3.1固定頭
數據格式:
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte 1 |
MQTT控制包類型(3) |
DUP |
服務質量(QoS) |
保留 |
||||
|
0 |
0 |
1 |
1 |
× |
× |
× |
|
byte 2 |
剩餘長度 |
|
3.3.1.1DUP
位置:第1個字節中的第3位
如果DUP標記設置爲0,它表示這個PUBLISH包(客戶端或服務端發送的)是第一次發送。反之,如果設置爲1,表示它可能是重複分發的了。
當服務端或客戶端需要重新發送一個PUBLISH包時,需要將DUP設置爲1[MQTT-3.3.1-1]。所有QoS值爲0的消息,它的DUP必須設置0[MQTT-3.3.1-2]。
當一個輸入的PUBLISH包經由服務端轉發給關注者時,其原始的DUP值是不會被轉發。輸出和輸入的PUBLISH包中DUP標記是分開設置的。這取決於這個包是否是重發[MQTT-3.3.1-3]。
非規範註釋
當接收端收到一個DUP爲1的控制包時,並不一定是重複包。
非規範註釋
DUP標記只是指定控制包自身是否重複,而無法判斷其所包含的應用消息的重複性。當客戶端接收到一個QoS爲1、DUP爲0的PUBLISH包時,雖然它的包標識符是唯一的,但是它包含的應用消息可能之前已經發送過。有關包的標識符的描述,2.3.1小節有詳盡的說明。
3.3.1.2QoS
位置:第1個字節中的第1-2位
這個字段指定應用消息交付的保證級別。QoS級別如下:
QoS級別 |
2(位) |
1(位) |
描述 |
0 |
0 |
0 |
最多交付一次 |
1 |
0 |
1 |
最少交付一次 |
2 |
1 |
0 |
只交付一次 |
- |
1 |
1 |
保留(不能使用) |
一個PUBLISH包中的QoS位不能全部設置爲1。不管是服務端還是客戶接收到一個QoS位全部都是1的PUBLISH包時,它們都會關閉網絡連接[MQTT-3.3.1-4]。
3.3.1.3RETAIN
位置:第1個字節中的第0位
如果客戶端向服務端發送一個RETAIN爲1的PUBLISH包時,服務端會保存它的應用消息和QoS,以便將來分發給它的訂閱者們(匹配它的主題名字的)[MQTT-3.3.1-5]。當一個新訂閱成功建立後,匹配這個主題的、最後保存的消息會被髮送到訂閱者,如果沒有,什麼都不做[MQTT-3.3.1-6]。如果服務端接收到一個QoS爲0、RETAIN爲1的消息,它會丟棄之前爲這個主題而保存的所有消息[MQTT-3.3.1-7]。關於存儲狀態的更多信息,請參考4.1小節。
當一個PUBLISH包發送到客戶端時,它必須設置RETAIN爲0,因爲它匹配的是一個已建立的關注,不管這個標記怎樣設置[MQTT-3.3.1-9]。
如果服務端接收到一個RETAIN爲1、包含0字節負載的PUBLISH包時,它會“正常地”處理這個包,然後轉發給訂閱過的客戶端。除此之外,會刪除所有跟這個主題名字相同的已存消息。這樣會導致後來訂閱這個主題的客戶端服務收到保存的信息[MQTT-3.3.1-10]。這裏的“正常地”的意思是,只有已存的客戶端才能接收到該消息。服務端不會保存一個0字節的、可保存消息[MQTT-3.3.1-11]。
如果一個由客戶端發送到服務端的PUBLISH包的RETAIN標記爲0,服務端不會保存這個消息,也不會刪除或覆蓋任何已經保存的信息[MQTT-3.3.1-12]。
非規範註釋
保存消息在發佈者不定期發送狀態消息的場景下很有用。一個的訂閱者可以接收到最新的狀態值。
剩餘長度字段
這個長度等於可變頭的長度加上負載的長度。
3.3.2可變頭
可變頭包含如下順序的字段:主題名稱、包標識符。
3.3.2.1主題名稱
主題名稱定義了被髮送負載數據的通道信息。
主題名稱必須放在PUBLISH包的可變頭中第一個字段。它必須是一個UTf-8編碼的字符串[MQTT-3.3.2-1]。
PUBLISH包中的主題名稱一定不能包含通配字符[MQTT-3.3.2-2]。
PUBLISH包中的主題名稱是服務端轉發的定位器,只會發送給那些和主題名稱匹配的訂閱者們,關於如何處理主題的匹配話題,請參考4.7小節[MQTT-3.3.2-3]。
然而,由於服務端可以重寫主題名稱,這導致,它的主題名稱可能跟原PUBLISH包的主題不一致。
3.3.2.2包標識符
包標識符只會出現在QoS爲1或2的PUBLISH包中。2.3.1小節提供更多關於包標識符的信息。
3.3.2.3非規範性的可變頭例子
值 |
|
主題名稱 |
a/b |
包標識符 |
10 |
3.3.3負載
負載包含着具體的應用消息。其內容和數據格式有具體的應用指定。它的長度可以用固定頭中的剩餘長度減去可變頭的長度得到。對於PUBLISH包來說,一個0字節的負載也是有效的。
3.3.4響應
PUBLISH包的接收者必須要要返回一個合適的響應。具體的響應內容取決於PUBLISH包中的QoS[MQTT-3.3.4.1]。
QoS級別 |
預期的響應 |
None |
|
QoS 1 |
PUBACK |
QoS 2 |
PUBREC |
3.3.5Actions
客戶端使用一個PUBLISH包發送一個應用消息給服務端,服務端會把這個消息分發給所有匹配的訂閱的客戶端。
當客戶端使用包含通配符的主題過濾器來構造訂閱時,它可能會讓客戶端的訂閱重疊,導致發送的消息會匹配到多個主題過濾器。這時,服務端必須要根據匹配訂閱的最大QoS去交付消息[MQTT-3.3.5-1]。此外,服務端鑑於訂閱的每個QoS,可能還要爲那些額外匹配的訂閱交付消息副本。
對於接收PUBLISH的接收端,在不同QoS級別下會有不同的操作,具體細節會在4.3小節講述。
如果服務端的實現不分發由客戶端發送的PUBLISH包;它沒有辦法通知客戶端。因此,要麼根據QoS規則構建一個合適的確認包,要麼只能關閉連接[MQTT-3.3.5-2]。
3.4PUBACK – 發佈確認
PUBACK包是QoS爲1的PUBLISH包的響應。
3.4.1固定頭
數據結構:
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte 1 |
MQTT控制包類型(4) |
保留 |
||||||
|
0 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
byte 2 |
剩餘長度(2) |
|||||||
|
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
剩餘長度
這裏只有可變頭的長度,PUBACK包只有2個字節。
3.4.2可變頭
這裏只包含了待確認的PUBLISH包的包標識符。
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
字節 1 |
包標識符 MSB |
|||||||
字節 2 |
包標識符 LSB |
3.4.3負載
PUBACK包沒有負載。
3.4.4Actions
會在4.3.2小節講解。
3.5PUBREC –發佈收到(QoS爲2的發佈收到,第1部)
PUBREC包是QoS爲2 PUBISH包的響應。它是QoS 2協議交互的第2個包。
3.5.1固定頭
數據結構:
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte 1 |
MQTT控制包類型(5) |
保留 |
||||||
|
0 |
1 |
0 |
1 |
0 |
0 |
0 |
0 |
byte 2 |
剩餘長度(2) |
|||||||
|
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
剩餘長度
這裏只有可變頭的長度,PUBREC包只有2個字節。
3.5.2可變頭
這裏只包含了待確認的PUBLISH包的包標識符。
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
字節 1 |
包標識符 MSB |
|||||||
字節 2 |
包標識符 LSB |
3.5.3負載
沒有負載。
3.5.4Actions
會在4.3.3小節講解。
3.6PUBREL –發佈釋放(QoS爲2的發佈收到,第2部)
PUBREL包是QoS爲2 PUBISH包的響應。它是QoS 2協議交互的第3個包。
3.6.1固定頭
數據結構:
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte 1 |
MQTT控制包類型(6) |
保留 |
||||||
|
0 |
1 |
1 |
0 |
0 |
0 |
1 |
0 |
byte 2 |
剩餘長度(2) |
|||||||
|
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
PUBREL包的固定頭第一個字節的3、2、1、0位是保留位,必須分別設置爲0、0、1和0。除此之外的值,服務端都會關閉連接[MQTT-3.6.1-1]。
剩餘長度
這裏只有可變頭的長度,PUBREC包只有2個字節。
3.6.2可變頭
這裏只包含了待確認的PUBLISH包的包標識符。
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
字節 1 |
包標識符 MSB |
|||||||
字節 2 |
包標識符 LSB |
3.6.3負載
沒有負載。
3.6.4Actions
會在4.3.3小節講解。
3.7PUBCOMP – 發佈完成(QoS爲2的發佈收到,第3部)
PUBCOMP包是QoS爲2 PUBISH包的響應。它是QoS 2協議交互的第4個包。
3.7.1固定頭
數據結構:
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte 1 |
MQTT控制包類型(7) |
保留 |
||||||
|
0 |
1 |
1 |
1 |
0 |
0 |
0 |
0 |
byte 2 |
剩餘長度(2) |
|||||||
|
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
剩餘長度
這裏只有可變頭的長度,PUBREC包只有2個字節。
3.7.2可變頭
這裏只包含了待確認的PUBLISH包的包標識符。
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|
字節 1 |
包標識符 MSB |
|||||||
字節 2 |
包標識符 LSB |
3.7.3負載
沒有負載
3.7.4Actions
會在4.3.3小節講解。
3.8SUBSCRIBE – 訂閱主題
SUBSCRIBE包是從客戶端發送到服務端,它會觸發服務端創建一個或多個訂閱。每個訂閱會登記客戶端感興趣的一個或多個主題。服務端在轉發PUBLISH包到客戶端的時候,就是匹配這些訂閱中的主題進行的。SUBSCRIBE還需要指定(爲每一個訂閱)最大的QoS,以保證服務端可以將應用消息發送到客戶端。
3.8.1固定頭
數據結構:
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte 1 |
MQTT控制包類型(8) |
保留 |
||||||
|
1 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
byte 2 |
剩餘長度 |
SUBSCRIBE包固定頭第1個字節的3、2、1、0位是保留位,必須分別設置爲0、0、1和0。除此之外的值,服務端都會關閉連接[MQTT-3.8.1-1]。
剩餘長度
可變頭的長度加上負載的長度
3.8.2可變頭
可變頭包含一個包標識符。更多關於包標識符的內容,請參考2.3.1小節。
3.8.2.1一個非規範性的可變頭例子
|
描述 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
包標識符 |
|||||||||
byte 1 |
包標識符MSB(0) |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
byte 2 |
包標識符LSB(10) |
0 |
0 |
0 |
0 |
1 |
0 |
1 |
0 |
3.8.3負載
SUBSCRIBE包的負載包含了一個主題過濾器列表,用於指明客戶端想要訂閱的主題。SUBSCRIBE負載中的主題過濾器必須使用UTF-8編碼的字符串[MQTT-3.8.3-1]。服務端應該支持包含通配符(4.7.1小節中定義)的主題過濾器。如果它不支持包含通配符的主題過濾器,它必須拒絕任何包含通配符的訂閱請求[MQTT-3.8.3-2]。緊隨其後的是一個被稱爲請求QoS(1字節表示)。基於這裏的最大QoS級別,服務可以將應用信息發佈給客戶端。
SUBSCRIBE包的負載必須至少包含一個主題過濾器/QoS對。一個沒有負載的SUBSCRIBE包是不符合協議規範的[MQTT-3.8.3-3]。查看4.8小節瞭解更多處理此錯誤的細節。
負載格式:
描述 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
主題過濾器 |
||||||||
byte 1 |
Length MSB |
|||||||
byte 2 |
Length LSB |
|||||||
byte 3…N |
主題過濾器 |
|||||||
請求的QoS |
||||||||
|
保留 |
QoS |
||||||
byte N+1 |
0 |
0 |
0 |
0 |
0 |
0 |
× |
請求QoS字節的前6位在本版本協議裏沒有使用。它們留作將來使用。在保留位上設置任何的非0或QoS的取值不是0、1、2時,服務端必須視其爲無效的SUBSCRIBE包,並關閉連接[MQTT-3.8.3-4]。
3.8.3.1非規範性的負載例子
主題名稱 |
“a/b” |
請求的QoS |
0x01 |
主題名稱 |
“c/d” |
請求的QoS |
0x02 |
3.8.4響應
當服務端接收到一個來自客戶端的SUBSCRIBE包時,服務端必須響應一個SUBACK包[MQTT-3.8.3-1]。且這個SUBACK的包標識符一定要和待應答的SUBSCRIBE的標識符相同[MQTT-3.8.4-2]。服務端允許在沒有收到SUBACK包前,開始發送匹配這個訂閱的PUBLISH包。
如果服務端接收到一個SUBSCRIBE包,其包含的主題過濾器跟一個已存在的訂閱中相同時,它必須用一個新的訂閱完全取代舊的訂閱。雖然這個新訂閱的主題過濾器和之前的一樣,但是它的最大QoS有可能不相同。而且,和它匹配的保存消息必須全部重發,正在發送當中的消息可以不中斷[MQTT-3.8.4-3]。如果沒有和已存在的訂閱中的主題過濾器相同,之後,會創建一個新的訂閱併發送與之匹配的保存消息。
如果服務端接收到的SUBSCRIBE包中包含多個主題過濾器時,它必須像處理連續單個SUBSCRIBE包一樣,只不過不是每個處理返回一個SUBACK包,而是把它的處理結果聯合在一個單獨的SUBACK包中返回[MQTT-3.8.4-4]。
服務端響應的SUBACK包中必須包含一個與沒個主題過濾器/QoS對對應返回碼。這個返回碼要麼表明訂閱被允許,要就是訂閱失敗[MQTT-3.8.4-5]。服務端可以授予一個比訂閱者請求更低QoS的級別。訂閱的響應負載中的QoS最小值必須由原始消息的QoS,它的最大值必須由服務端授予。當源消息以QoS爲1發佈,且最大的QoS被授予爲0時,可以允許服務端重複發送該消息的副本[MQTT-3.8.4-6]。
非規範性例子
如果一個訂閱客的戶端被授予一個特定的主題過濾器的最大QoS爲1,那麼,一個QoS爲0的匹配應用信息會被以QoS爲0發送到客戶端。這個意味着客戶端最多隻能接收一次該消息。另一個場景,當一個QoS爲2的匹配應用消息會先被服務端降級爲QoS爲1,然後才發送到客戶端,這就意味着客戶端可能會接收到重複的消息。
如果訂閱的客戶端被授予的最大QoS爲0,之後,一個原來QoS爲2的應用消息匹配到該訂閱時,客戶端可能會丟失掉這個消息,但是服務端絕對不會再次發送它。一個QoS爲1的消息被匹配時,該客戶端可能丟失該消息或重複收到它。
非規範性註釋
訂閱一個QoS爲2的主題過濾器,相當於你告訴服務端,我只想接收匹配這個主題過濾器且以QoS發佈的消息。這意味着,服務端有責任決定交付消息的QoS級別,除此之外,訂閱者也需要服務端對QoS進行適當的降級,以適應訂閱者的使用場景。
3.9SUBACK – 訂閱確認
SUBACK包是服務端給客戶端發送SUBSCRIBE包的響應,它是接收的憑證和服務端的處理結果。一個SUBACK包含一系列的返回碼,它指定了每個訂閱的被授予的最大QoS級別。
3.9.1固定頭
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte 1 |
MQTT控制包類型(9) |
保留 |
||||||
|
1 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
byte 2 |
剩餘長度 |
剩餘長度字段
可變頭長度(2字節)加上負載的長度。
3.9.2可變頭
可變頭包含來自待確認的SUBSCRIBE包的包標識符。其數據格式如下:
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|
字節 1 |
包標識符 MSB |
|||||||
字節 2 |
包標識符 LSB |
3.9.3負載
負載包含一系列的返回碼。每個返回碼和待確認的SUBSCRIBE包中的主題過濾器對應。它們的順序必須和待確認SUBSCRIBE包中的主題過濾器列一致[MQTT-3.9.3-1]。
負載中的返回碼使用一個字節表示,其格式如下:
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|
返回碼 |
|||||||
byte 1 |
0 |
0 |
0 |
0 |
0 |
× |
× |
合法的返回碼:
0x00–成功(最大QoS爲 0)
0x01–成功(最大QoS爲 1)
0x02–成功(最大QoS爲 2)
0x80–失敗
除了上面列出的返回碼外,其他的值都是SUBACK的保留值,所以一定不能使用[MQTT-3.9.3-2]。
3.9.3.1一個非規範性的負載例子
下面的一個例子簡要的描述了一下負載的字節格式。
成功–最大QoS爲 0 |
0 |
成功–最大QoS爲 2 |
2 |
失敗 |
128 |
|
描述 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte 1 |
成功–最大QoS爲 0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
byte 2 |
成功–最大QoS爲 2 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
byte 3 |
失敗 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
3.10UNSUBSCRIBE – 取消訂閱主題
UNSUBSCRIBE是客戶端發送到服務端取消訂閱主題的包。
3.10.1固定頭
數據格式:
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte 1 |
MQTT控制包類型(10) |
保留 |
||||||
|
1 |
0 |
1 |
0 |
0 |
0 |
0 |
0 |
byte 2 |
剩餘長度 |
UNSUBSCRIBE包固定頭第1個字節的3、2、1、0位是保留位,必須分別設置爲0、0、1和0。除此之外的值,服務端都會關閉連接[MQTT-3.10.1-1]。
剩餘長度字段
可變頭長度(2字節)加上負載的長度。
3.10.2可變頭
可變頭包含一個包標識符。更多關於包標識符的細節請參考2.3.1小節。
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
字節 1 |
包標識符 MSB |
|||||||
字節 2 |
包標識符 LSB |
3.10.3負載
UNSUBSCRIBE的負載包含一個客戶端想要退訂的主題過濾器集。UNSUBSCRIBE包中的主題過濾器必須使用UTF-8編碼的字符串[MQTT-3.10.3-1]。
UNSUBSCRIBE包中的負載必須至少包含一個主題過濾器。一個沒有負載的UNSUBSCRIBE包定義是不符合協議規範的[MQTT-3.10.3-2]。查看4.8小節獲取更多處理此錯誤的信息。
3.10.3.1一個非規範性的負載例子
下面的例子簡要的描述UNSUBSCRIBE的負載:
主題過濾器 |
“a/b” |
主題過濾器 |
“c/d” |
|
描述 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
主題過濾器 |
|||||||||
byte 1 |
Length MSB(0) |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
byte 2 |
Length LSB(3) |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
1 |
byte 3 |
‘a’(0x61) |
0 |
1 |
1 |
0 |
0 |
0 |
0 |
1 |
byte 4 |
‘/’(0x2F) |
0 |
0 |
1 |
0 |
1 |
1 |
1 |
1 |
byte 5 |
‘b’(0x62) |
0 |
1 |
1 |
0 |
0 |
0 |
1 |
0 |
主題過濾器 |
|||||||||
byte 7 |
Length MSB(0) |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
byte 8 |
Length LSB(3) |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
1 |
byte 9 |
‘c’(0x63) |
0 |
1 |
1 |
0 |
0 |
0 |
1 |
1 |
byte 10 |
‘/’(0x2F) |
0 |
0 |
1 |
0 |
1 |
1 |
1 |
1 |
byte 11 |
‘d’(0x64) |
0 |
1 |
1 |
0 |
0 |
1 |
0 |
0 |
3.10.4響應
UNSUBSCRIBE包中主題過濾器會與保存服務端的過濾器集進行字符比較。如果可以精確的匹配到過濾器,之後,它會刪除這個訂閱,反之,不做任何額外的處理[MQTT-3.10.4-1]。
如果一個服務器刪除了一個訂閱:
l 它必須停止交付新增的任何消息給該客戶端[MQTT-3.10.4-2]
l 它必須完成交付退訂發生之前的QoS爲1和2的消息[MQTT-3.10.4-3]
l 它可以繼續給客戶端交付已經在消息緩存中的消息
服務端必須爲UNSUBSCRIBE請求返回一個UNSUBACK響應包。並且這個UNSUBACK包的包標識符要和原來的UNSUBSCRIBE的一樣[MQTT-3.10.4-4]。哪怕沒有主題訂閱被刪除,服務端也必須返回一個UNSUBACK響應包[MQTT-3.10.4-5]。
如果服務端接收到的UNSUBSCRIBE包中包含的是多個主題過濾器,那麼,它必須如處理連續單個主題過濾器那樣處理它,只不過將其處理結果放到一個單獨的UNSUBACK包中返回[MQTT-3.10.4-6]。
3.11UNSUBACK – 取消訂閱確認
UNSUBACK是服務端發給客戶端UNSUBSCRIBE請求的響應,它是服務端接收完成的憑證。
3.11.1固定頭
數據格式:
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte 1 |
MQTT控制包類型(11) |
保留 |
||||||
|
1 |
0 |
1 |
1 |
0 |
0 |
0 |
0 |
byte 2 |
剩餘長度 |
剩餘長度字段
這跟可變頭的長度一樣。對於UNSUBACK包,它的取值是2。
3.11.2可變頭
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
字節 1 |
包標識符 MSB |
|||||||
字節 2 |
包標識符 LSB |
3.11.3負載
沒有負載。
3.12PINGREQ – PING 請求
PINGREQ包被客戶端發送服務端,用於:
1.當客戶端沒有任何有效的控制包要發送到服務端時,爲了讓服務端知道客戶還“活着”(心跳機制)。
2.服務端用於響應確認請求,表明它還“活着”。
3.用於檢測網絡連接是否可用
這個包被用在“保活處理場景裏”,更多的細節請參考3.1.2.10小節。
3.12.1固定頭
數據格式:
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|
byte 1 |
MQTT控制包類型(12) |
保留 |
||||||
|
1 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
byte 2 |
剩餘長度(0) |
|||||||
|
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
3.12.2可變頭
PINGREQ包沒有可變頭。
3.12.3負載
沒有負載。
3.12.4響應
服務端必須爲PINGREQ請求返回一個PINGRESP響應。它表明服務端還“活着”[MQTT-3.12.4-1]。
3.13PINGRESP – PING 響應
PINGRESP是服務端爲PINGREQ請求返回一個PINGRESP響應。它表明服務端還“活着”。這個包被用在“保活處理場景裏”,更多的細節請參考3.1.2.10小節。
3.13.1固定頭
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte 1 |
MQTT控制包類型(13) |
保留 |
||||||
|
1 |
1 |
0 |
1 |
0 |
0 |
0 |
0 |
byte 2 |
剩餘長度(0) |
|||||||
|
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
3.13.2可變頭
沒有可變頭。
3.13.3負載
沒有負載。
3.14DISCONNECT – 斷開通知
DISCONNECT包是客戶端發送到服務端的最後的包。它表明客戶端將要斷開連接。
3.14.1固定頭
數據格式:
位 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte 1 |
MQTT控制包類型(14) |
保留 |
||||||
|
1 |
1 |
1 |
0 |
0 |
0 |
0 |
0 |
byte 2 |
剩餘長度(0) |
|||||||
|
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
服務端必須校驗斷開連接包中的保留位,是否是設置爲0,如果有非0的設置,關閉網絡連接[MQTT-3.14.1-1]。
3.14.2可變頭
沒有可變頭。
3.14.3負載
沒有負載。
3.14.4響應
當客戶端發送一個DISCONNECT包後:
l 必須關閉網絡連接[MQTT-3.14.4-1]。
l 該網絡連接不能再發送任何控制包[MQTT-3.14.4-2]。
服務端接收到DISCONNECT後:
l 必須丟掉與這個連接關聯的、且還沒有發送的消息,如3.1.2.5小節描述那樣[MQTT-3.14.4-3]。
4.操作行爲
4.1保存的狀態
爲了提供有保證的服務質量(QoS),客戶端和服務端的會話狀態保存時很有必要的。客戶端和服務端必須保存整個會話生命週期內的狀態[MQTT-4.1.0-1]。一個會的生命週期至少要和它活躍的連接一樣長[MQTT-4.1.0-2]。
在服務端,保存消息不是會話狀態職責範圍。服務端會保存這些消息,直到客戶端刪除它們。
非規範性註釋
客戶端和服務的存儲容量肯定是有限,且可能會受到其他的管理的約束,比如像回覆的最大保存時間。管理員的一些行爲也可能會讓會話狀態丟失,這包括一些自定義條件的自動響應。這會導致整個會話失效。這些行爲可能是資源的限制或者其他的操作原因引起的。因此,我們要謹慎的對客戶端和服務端的存儲能力進行合理的評估,確保他們足夠。
非規範性註釋
硬件或軟件上的缺陷也可能會導致存儲的會話狀態丟失或損壞。
非規範性註釋
管理員的一些操作,軟硬件的缺陷都可能會導致一些正常操作的狀態丟失。一個管理員操作可以是自定條件的自動響應。例如,服務端可以依據一些外部的條件,決定一個或個多消息是否能交付。
非規範性註釋
MQTT的使用者應該去合理的評估MQTT客戶端和服務端實現的存儲能力,確保他們是否能夠達到你們的要求。
4.1.1一個非規範性的例子
例如,用戶希望收集電錶的度數時,他們可能需要使用QoS爲1的消息,因爲他們需要防止度數在網絡傳輸過程丟失的可能性,然而,如果他們可以持續供電情況下,也可以把這些丟失風險低的數據存保存在內存中。
相反,一個停車場計費應用服務,爲了避免發送消息的丟失,在消息發送之前,都會先把它強制寫入非易失的記憶體(硬盤等)中。
4.2網絡連接
MQTT協議需要一個基礎的傳輸器,它爲客戶端到服務端或服務端到客戶端提供有序的、無損的、基於字節流通信協議。
4.3服務質量級別和協議流
4.3.1QoS 0:最多交付一次
4.3.2QoS 1:至少交付一次
4.3.3QoS 2:有且只有交付一次
4.4消息交付重試
當客戶端與clean session爲0重連的時候,客戶端和服務端需要重發任何沒有確認的PUBLISH包(QoS > 0)和PUBREL(使用原包的包標識符)[MQTT-4.4.0-1]。
4.5消息接收憑證
4.6消息順序
客戶端實現協議流時,必須遵守下面的規則:
l 當它重發任何PUBLISH包,它的發送的順序必須和原來的一致(QoS爲1和2)
l 它必須按照接收到的PUBLISH包的相應順序發送回覆PUBACK(QoS 爲1)
l 它必須按照接收到的PUBLISH包的相應順序發送回覆PUBREC(QoS 爲2)
l 它必須按照接收到的PUBLISH包的相應順序發送回覆PUBREL(QoS 爲2)
4.7主題名稱和主題過濾器
4.7.1主題通配符
主題層級分隔符的使用,可以讓主題名稱擁有層級結構。如果它存在,他會將主題名字切分成多個主題層級。一個訂閱的主題過濾器可以包含特定的通配符,這樣你就可以使用一個訂閱請求訂閱多個主題。通配符可以被主題過濾器使用,但是,主題名稱一定不能使用通配符[MQTT-4.7.1-1]。
4.7.1.1主題層級分隔符
正斜槓(“/” U+002F)被用作主題樹裏的每層之間的分隔符,並且爲主題名稱提供了繼承的能力。主題層級分隔符可以出現在主題過濾器或主題名稱的任何位置。
4.7.1.2多級通配符
井號(“#” U+0023)是一個通配符,可以匹配主題內的任意層級。多級通配代表父級和任意的子級。它必須由“#”本身或跟隨一個層級分隔符來指定。無論什麼情況下,在主題過濾器裏,它(“#”)必須是最後的字符[MQTT-4.7.1-2]。
非規範性註釋
例如,如果一個客戶端訂閱了“sport/tennis/player1/#”,它可以收到發佈到如下主題下的消息:
l “sport/tennis/player1”
l “sport/tennis/player1/ranking”
l “sport/tennis/player1/score/wimbledon”
非規範性註釋
l “sport/#”也可以匹配單個“sport”,因爲#包含父級。
l “#” 是合法的,將會接收每個應用消息
l “sport/tennis#”是不合法
l “sport/tennis/#/ranking”也是不合法
4.7.1.3單級通配符
單級的通配符用加號(‘+’ U+002B)來表示。
單級通配符可以在主題過濾器中的任何層級上,包括第1和最後一層。如果在過濾器的某一層使用了它,那麼它需要佔據整層,既這層只能有它一個字符[MQTT-4.7.1-3]。在過濾器裏,它可以在多個層級下使用,也可以結合多層級通配符來使用。
非規範性註釋
例如,“sport/tennis/+”可以匹配”sport/tennis/player1”和“sport/tennis/player2”,但不能匹配“sport/tennis/player1/ranking”。同理,因爲單級通配符只能匹配單個層級,因此,”sport/+”不能匹配“sport”,但是可以匹配“sport/”。
非規範性註釋
l “+”是合法
l “+/tennis/#”是合法
l “sport+”是不合法
l “sport/+/player1”是合法
l “/finance”可以匹配“+/+”和“/+”,但是不匹配“+”
4.7.2以$開頭的主題
服務端一定不會匹配以通配符(#/+)開頭的主題過濾器和以$號開頭的主題名稱[MQTT-4.7.2-1]。服務端應該阻止客戶端使用類似這樣的主題名稱或主題過濾器和其他的客戶端通信。服務端實現可以使用以$開頭主題名稱去做其他用途。
非規範性註釋
l $SYS/ 作爲主題名稱前綴已被廣泛使用,其包含服務端特定信息或控制APIs
l 應用不能使用以$開頭的主題名稱,無論基於什麼用途
非規範性註釋
l 訂閱了“#”訂閱者,將接收不到任何發佈到以$開頭的主題消息
l 訂閱了“+/monitor/Clients”訂閱者,將接收不到任何發佈到“$SYS/monitor/Clients”的消息
l 訂閱了“$SYS/#”訂閱者,將接收不到發佈到以“$SYS/”開頭的主題消息
l 訂閱了“$SYS/monitor/+”的訂閱者,將會接收到發佈到“$SYS/monitor/Client”消息
l 爲了讓客戶端可以同時接收來自以$SYS開頭和不以$開頭的主題消息,它必須要訂閱“#”和“$SYS/#”主題。
4.7.3主題的語法和用法
下面的規則適用於主題或主題過濾器:
l 所有的主題名稱或主題過濾器的字符至少包含一個字符[MQTT-4.7.3-1]
l 主題名稱和主題過濾器是區分大小寫的
l 主題名稱和主題過濾器可以包含空格字符
l “/”字符放在主題名稱和主題過濾器的頭部和尾部是不一樣的
l 由單個“/”定義的主題名稱或主題過濾器是合法
l 主題名稱和主題過濾器一定不能不含null字符(Unicode U+0000)[MQTT-4.7.3-2]
l 主題名稱和主題過濾器都要使用UTF-8編碼的字符串定義,它們最大編碼字節長度一定不能超過65535[MQTT-4.7.3-3]。關於UTF-8字符串編碼,請參考1.5.3小節。
這裏沒有限制主題名稱(或主題過濾器)的層級數,只對編碼的總長度有限制。因爲它們的編碼字節數用2個字節表示,所以最大隻能到65535。
當服務端執行訂閱匹配的時候,它一定不會處理任何標準化(保留的),或使用不符合規範字符修改或替換的主題名稱和主題過濾器[MQTT4.7.3-4]。
沒有包含通配符的主題或主題過濾器都是採用同層級內字符比較進行匹配,如果完全匹配,那麼匹配成功,反之,不成功。
非規範性註釋
UTF-8編碼規則讓主題名稱或主題過濾器的比較變得簡單,你可以直接比較它們的UTF-8字節,也可以比較它們的原始字符。
非規範性註釋
l “ACCOUNTS”和“Accounts”是兩個不同的主題名稱
l “Accounts payable”也是一個合法的主題名稱
l “/finance”和“finance”不相同
一個應用消息會被服務端分轉發到每個與其主題名稱或主題過濾器匹配的客戶端訂閱中,主題資源可以由管理員預先創建好,也可以由服務端在收到第一次訂閱或一個應用信息時,動態去創建。服務端也可以使用一個安全中間件,使用授權方式去限制客戶端對主題資源的使用。
4.8處理錯誤
如果沒有特別的說明,如果客戶端或服務端接收到的控制包包含不符合協議規範的設置,客戶端或服務端必須關閉這個連接[MQTT-4.8.0-1]。MQTT的客戶端或服務端實現可能會遇到一些瞬態(如,內部的緩衝區滿了)的錯誤,導致處理MQTT包失敗。
如果客戶端或服務端正在處理一個輸入的控制包時,遇到了一個瞬態的錯誤,它必須關閉與這個包關聯的連接[MQTT-4.8.0-2]。如果服務端檢測到一個瞬態的錯誤,它不應該關閉連接或影響到與其它客戶端交互。