背景
上一講 MQTT 協議學習:通信報文的構成介紹了在MQTT通信中,各報文的通信流程;從本講開始,我們開始介紹實際中使用的報文,以及它們的組成。
CONNECT - 連接請求 報文
客戶端到服務端的網絡連接建立后,客戶端發送給服務端的第一個報文必須是CONNECT, 連接服務端
報文。
在一個網絡連接上,客戶端只能發送一次CONNECT報文。服務端必須將客戶端發送的第二個CONNECT報文當作協議違規處理並斷開客戶端的連接。
服務端可以檢查CONNECT報文的內容是不是滿足任何進一步的限制,可以執行身份驗證和授權檢查。如果任何一項檢查沒通過,它應該發送一個適當的、返回碼非零的CONNACK響應,並且必須關閉這個網絡連接。
有效載荷包含一個或多個編碼的字段。包括客戶端的唯一標識符,Will主題,Will消息,用戶名和密碼。除了客戶端標識之外,其它的字段都是可選的,基於標志位來決定可變報頭中是否需要包含這些字段。
CONNECT 報文的固定頭
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
byte 1 | MQTT報文類型 (0x1) | 保留位(0x0) |
CONNECT 的 可變報頭
CONNECT報文的可變報頭按下列次序包含四個字段:協議名(Protocol Name),協議級別(Protocol Level),連接標志(Connect Flags)和保持連接(Keep Alive)。
0040 10 30 00 04 4d 51 54 54 04 c2 00 3c 00 17 6d 6f .0..MQTT...<..mo
0050 73 71 2d 66 5a 4a 69 30 75 51 78 38 4d 6b 55 64 sq-fZJi0uQx8MkUd
0060 55 61 42 52 5a 00 05 61 64 6d 69 6e 00 04 72 6f UaBRZ..admin..ro
0070 6f 74 ot
協議名稱 Protocol Name
協議名稱(Protocol Name):值固定為字符 “MQTT”的UTF-8編碼的字符串,MQTT規范的后續版本不會改變這個字符串的偏移和長度。占用6個字節。
說明 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
---|---|---|---|---|---|---|---|---|---|
協議名 | |||||||||
byte 1 | 長度 MSB (0) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
byte 2 | 長度 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 |
協議級別 Protocol Level
協議版本(Protocol Level):對 MQTT 3.1.1 來說,值為 4。占用1個字節。
對於3.1.1版協議,協議級別字段的值是4(0x04)。如果發現不支持的協議級別,服務端必須給發送一個返回碼為0x01(不支持的協議級別)的CONNACK報文響應CONNECT報文,然后斷開客戶端的連接
協議級別 | 說明 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|
byte 7 | Level(4) | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
連接標志 Connect Flags
連接標志字節包含一些用於指定MQTT連接行為的參數。它還指出有效載荷中的字段是否存在。 byte8[0]必須為0,否則斷開連接。
bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
連接標志 | User Name Flag | Password Flag | Will Retain | Will Qos | Will Qos | Will Flag | Clean Session | Reserver |
byte 8 | X | X | X | X | X | X | X | 0 |
關於 遺囑(byte8[5:2]) 有關的知識可以參考《MQTT 協議學習:Retained(保留消息) 和LWT(最后遺囑)》
關於 會話(byte8[1]) 有關知識可以參考 《MQTT 協議學習:003-MQTT協議中的Qos等級》
用戶名標識(User Name Flag):消息體中是1否0有用戶名字段。
密碼標識(Password Flag):消息體中是1否0有密碼字段。
遺囑消息 Retain 標識(Will Retain):標識遺囑消息是1否0是 Retain 消息。
如果 遺囑標識 被設置為0,遺囑保留(Will Retain)標志也必須設置為0。
如果遺囑標志被設置為1:
- 如果遺囑保留被設置為0,服務端必須將遺囑消息當作非保留消息發布。
- 如果遺囑保留被設置為1,服務端必須將遺囑消息當作保留消息發布。
遺囑消息 QOS 標識(Will Qos):標識遺囑消息的 Qos,2bit。
如果遺囑標志被設置為0,遺囑QoS也必須設置為0(0x00)
遺囑標識(Will Flag):標識是1否0使用遺囑消息。
會話清除標識(Clean Session):標識 Client 是0否1建立一個持久化的會話。當 Clean Session 的標識設為 0 時,代表 Client 希望建立一個持久會話的連接,Broker 將存儲該 Client 訂閱的主題和未接受的消息,否則(設置為1) Broker 不會存儲這些數據,同時在建立連接時清除這個 Client 之前存在的持久化會話所保存的數據。持久會話只在 QoS 等級 大於等於 1 的消息 中有效。
下面是引用中文文檔的2段描述:
- 如果清理會話(Clean Session)標志被設置為0,服務端必須基於當前會話(使用客戶端標識符識別)的狀態恢復與客戶端的通信。如果沒有與這個客戶端標識符關聯的會話,服務端必須創建一個新的會話。在連接斷開之后,當連接斷開后,客戶端和服務端必須保存會話信息。當清理會話標志為0的會話連接斷開之后,服務端必須將之后的QoS 1和QoS 2級別的消息保存為會話狀態的一部分,如果這些消息匹配斷開連接時客戶端的任何訂閱。服務端也可以保存滿足相同條件的QoS 0級別的消息。
- 如果清理會話(Clean Session)標志被設置為1,客戶端和服務端必須丟棄之前的任何會話並開始一個新的會話。會話僅持續和網絡連接同樣長的時間。與這個會話關聯的狀態數據不能被任何之后的會話重用。
小技巧:
要確保不丟失連接斷開期間的消息,需要使用QoS 1或 QoS 2級別,同時將清理會話標志設置為0。
清理會話標志0的客戶端連接時,如果打算在之后的某個時間點重連到這個服務端,客戶端連接應該只使用清理會話標志0。當客戶端決定之后不再使用這個會話時,應該將清理會話標志設置為1最后再連接一次,然后斷開連接。
連接保活(Keep Alive): 設置一個單位為秒的時間間隔,指在 client 傳輸完成一個控制報文的時刻到發送下一個報文的時刻,client 與 broker 兩者之間允許空閑的最大時間間隔。,可以參考:《MQTT 協議學習:Keep Alive 和連接保活》。
- Keep Alive 的最大值為 18 小時 12 分 15 秒(65535秒,實際上就是 0xff, 1個字);
- Keep Alive 值如果設為 0 的話,代表不使用 Keep Alive 機制。
Bit | 7 6 5 4 3 2 1 0 |
---|---|
byte 9 | 保持連接 Keep Alive MSB |
byte 10 | 保持連接 Keep Alive LSB |
CONNECT 的 有效載荷 Payload
CONNECT報文的有效載荷(payload)包含一個或多個以長度為前綴的字段,可變報頭中的標志決定是否包含這些字段。
如果包含的話,必須按這個順序出現:客戶端標識符,遺囑主題,遺囑消息,用戶名,密碼。
客戶端標識符 Client Identifier
客戶端標識符 (Client Id) 必須存在而且必須是CONNECT報文有效載荷的第1個字段,必須用UTF-8進行編碼。
這個字段由一個兩字節的長度和客戶端標識符的實際部分組成,表示為零字節或多個字節序列。長度給出了跟在后面的數據的字節數,不包含長度字段本身占用的兩個字節。
服務端使用客戶端標識符 (Client Id) 識別客戶端。連接服務端的每個客戶端都有唯一的客戶端標識符(Client Id)。客戶端和服務端都必須使用ClientId識別兩者之間的MQTT會話相關的狀態,它是在處理QoS級別1和2的消息ID中的關鍵。
服務端必須允許1到23個字節長的UTF-8編碼的客戶端標識符,客戶端標識符只能包含大小寫字母和數字
即 : "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
UTF-8 編碼字符串的結構 Structure of UTF-8 encoded strings
二進制位 | 7-0 |
---|---|
byte 1 | 字符串長度的最高有效字節(MSB) |
byte 2 | 字符串長度的最低有效字節(LSB) |
byte 3 …. | 如果長度大於0,這里是UTF-8編碼的字符數據。 |
服務端可以允許編碼后超過23個字節的客戶端標識符 (ClientId)。服務端可以允許包含非大小寫字母和數字字符的客戶端標識符。
服務端可以允許客戶端提供一個零字節的客戶端標識符 (ClientId) ,如果這樣做了,服務端必須將這看作特殊情況並分配唯一的客戶端標識符給那個客戶端。然后它必須假設客戶端提供了那個唯一的客戶端標識符,正常處理這個CONNECT報文。
如果服務端拒絕了這個ClientId,它必須發送返回碼為0x02(表示標識符不合格)的CONNACK報文響應客戶端的CONNECT報文,然后關閉網絡連接。
如果客戶端提供了一個零字節的客戶端標識符,它必須同時將清理會話標志設置為1。
如果客戶端提供的Client Id為零字節且清理會話標志為0,服務端必須發送返回碼為0x02(表示標識符不合格)的CONNACK報文響應客戶端的CONNECT報文,然后關閉網絡連接。
對於服務端來說:如果Client Id表明客戶端已經連接到這個服務端,那么服務端必須斷開原有的客戶端連接。
遺囑主題 Will Topic
如果遺囑標志被設置為1,有效載荷的下一個字段是遺囑主題(Will Topic)。遺囑主題必須UTF-8編碼字符串。
這個字段由一個兩字節的長度和遺囑主題的實際部分組成,表示為零字節或多個字節序列。長度給出了跟在后面的數據的字節數,不包含長度字段本身占用的兩個字節。
二進制位 | 7-0 |
---|---|
byte 1 | 字符串長度的最高有效字節(MSB) |
byte 2 | 字符串長度的最低有效字節(LSB) |
byte 3 …. | 如果長度大於0,這里是UTF-8編碼的字符數據。 |
遺囑消息 Will Message
如果遺囑標志被設置為1,有效載荷的下一個字段是遺囑消息。遺囑消息定義了將被發布到遺囑主題的應用消息。
這個字段由一個兩字節的長度和遺囑消息的有效載荷組成,表示為零字節或多個字節序列。長度給出了跟在后面的數據的字節數,不包含長度字段本身占用的兩個字節。
二進制位 | 7-0 |
---|---|
byte 1 | 字符串長度的最高有效字節(MSB) |
byte 2 | 字符串長度的最低有效字節(LSB) |
byte 3 …. | 如果長度大於0,這里是UTF-8編碼的字符數據。 |
遺囑消息被發布到遺囑主題時,它的有效載荷只包含這個字段的數據部分,不包含開頭的兩個長度字節。
用戶名 User Name
如果用戶名(User Name)標志被設置為1,有效載荷的下一個字段就是它。用戶名必須是UTF-8編碼字符串。服務端可以將它用於身份驗證和授權。
這個字段由一個兩字節的長度和用戶名的實際數據組成,表示為零字節或多個字節序列。長度給出了跟在后面的數據的字節數,不包含長度字段本身占用的兩個字節。
Bit | 7 - 0 |
---|---|
byte 1 | 數據長度 MSB |
byte 2 | 數據長度 LSB |
byte 3 …. | 如果長度大於0,這里就是數據部分 |
密碼 Password
如果密碼(Password)標志被設置為1,有效載荷的下一個字段就是它。密碼字段包含一個兩字節的長度字段,長度表示二進制數據的字節數(不包含長度字段本身占用的兩個字節),后面跟着0到65535字節的二進制數據。
Bit | 7 - 0 |
---|---|
byte 1 | 數據長度 MSB |
byte 2 | 數據長度 LSB |
byte 3 …. | 如果長度大於0,這里就是數據部分 |
CONNECT 連接成功以后的響應
服務器對於 CONNECT 請求的驗證步驟:
1)網絡連接建立后,如果服務端在合理的時間內沒有收到CONNECT報文,服務端應該關閉這個連接。
2)服務端必須按照3.1節的要求驗證CONNECT報文,如果報文不符合規范,服務端不發送CONNACK報文直接關閉網絡連接。
3)服務端可以檢查CONNECT報文的內容是不是滿足任何進一步的限制,可以執行身份驗證和授權檢查。如果任何一項檢查沒通過,按照3.2節的描述,它應該發送一個適當的、返回碼非零的CONNACK響應,並且必須關閉這個網絡連接。
如果驗證成功,服務端會執行下列步驟。
1)如果Client Id表明客戶端已經連接到這個服務端,那么服務端必須斷開原有的客戶端連接。
2)服務端必須按照會執行清理會話的過程。
3)服務端必須發送返回碼為零的CONNACK報文作為CONNECT報文的確認響應。
4)開始消息分發和保持連接狀態監視。
允許客戶端在發送CONNECT報文之后,可以不需要等待服務端的CONNACK報文立即發送其它的控制報文。如果服務端拒絕了客戶端的CONNECT請求,那么它不能處理客戶端在CONNECT報文之后發送的任何數據。
CONNACK – 確認連接請求 報文
服務端發送CONNACK報文響應從客戶端收到的CONNECT報文。服務端發送給客戶端的第一個報文必須是CONNACK。
如果客戶端在合理的時間內沒有收到服務端的CONNACK報文,客戶端應該關閉網絡連接。
CONNACK 的固定頭中的 剩余長度為2。(byte2 = 0x02)
# 抓包情況
MQ Telemetry Transport Protocol, Connect Ack
Header Flags: 0x20, Message Type: Connect Ack
Msg Len: 2
Acknowledge Flags: 0x00
0000 000. = Reserved: Not set
.... ...0 = Session Present: Not set
Return Code: Connection Accepted (0)
0040 20 02 00 00 ...
CONNACK 的 可變報頭
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
---|---|---|---|---|---|---|---|---|
連接確認標志 | ||||||||
byte 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | SP |
連接返回碼 | ||||||||
byte 2 | X | X | X | X | X | X | X | X |
連接確認標志 Connect Acknowledge Flags
連接確認標志 Connect Acknowledge Flags 只用了第0位。
byte1[7:1] 是保留位且必須設置為0;第0 (也叫 SP)位(byte1[0]) 是當前會話(Session Present)標志。
如果服務端收到清理會話(Clean Session)標志為1的連接,除了將CONNACK報文中的返回碼設置為0之外,還必須將CONNACK報文中的當前會話設置(Session Present)標志為0 。
如果服務端收到一個Clean Session為0的連接,當前會話標志的值取決於服務端是否已經保存了ClientId對應客戶端的會話狀態。
- 如果服務端已經保存了會話狀態,它必須將CONNACK報文中的SP設置為1。
- 如果服務端沒有已保存的會話狀態,它必須將CONNACK報文中的SP設置為0;還需要將CONNACK報文中的返回碼設置為0。(此時代表連接成功)
SP使服務端和客戶端在是否有已存儲的會話狀態上保持一致。
一旦完成了會話的初始化設置,已經保存會話狀態的客戶端將期望服務端維持它存儲的會話狀態。如果客戶端從服務端收到的當前的值與預期的不同,客戶端可以選擇繼續這個會話或者斷開連接。客戶端可以丟棄客戶端和服務端之間的會話狀態,方法是,斷開連接,將清理會話標志設置為1,再次連接,然后再次斷開連接。
如果服務端發送了一個包含非零返回碼的CONNACK報文,它必須將當前會話標志設置為0
連接返回碼 Connect Return code
連接返回碼 Connect Return code 是 :連接返回碼字段使用一個字節的無符號值,在下表中列出。
值 | 返回碼響應 | 描述 |
---|---|---|
0 | 0x00連接已接受 | 連接已被服務端接受 |
1 | 0x01連接已拒絕,不支持的協議版本 | 服務端不支持客戶端請求的MQTT協議級別 |
2 | 0x02連接已拒絕,不合格的客戶端標識符 | 客戶端標識符是正確的UTF-8編碼,但服務端不允許使用 |
3 | 0x03連接已拒絕,服務端不可用 | 網絡連接已建立,但MQTT服務不可用 |
4 | 0x04連接已拒絕,無效的用戶名或密碼 | 用戶名或密碼的數據格式無效 |
5 | 0x05連接已拒絕,未授權 | 客戶端未被授權連接到此服務器 |
6-255 | 保留 |
如果服務端收到一個合法的CONNECT報文,但出於某些原因無法處理它,服務端應該嘗試發送一個包含非零返回碼(表格中的某一個)的CONNACK報文。如果服務端發送了一個包含非零返回碼的CONNACK報文,那么它必須關閉網絡連接。
CONNACK 的 有效載荷
無。
DISCONNECT –斷開連接
DISCONNECT報文是客戶端發給服務端的最后一個控制報文。表示客戶端正常斷開連接。
DISCONNECT
數據包沒有可變頭(Variable header)和消息體(Payload),那么,DISCONNECT
報文的全部內容(共2個字節)就是 : 0xe0 0x00