歡迎閱讀 MQTT 5.0 報文系列 的第二篇文章。在上一篇中,我們已經介紹了 MQTT 5.0 的 CONNECT 和 CONNACK 報文。現在,我們將介紹在 MQTT 中用於傳遞應用消息的 PUBLISH 報文以及它的響應報文。
不管是客戶端向服務端發佈消息,還是服務端向訂閱端轉發消息,都需要使用 PUBLISH 報文。決定消息流向的主題、消息的實際內容和 QoS 等級,都包含在 PUBLISH 報文中。
客戶端與服務端在消息傳遞的過程中,除了 PUBLISH 報文,還會用到 PUBACK、PUBREC、PUBREL、PUBCOMP 這四個報文,它們分別用於實現 MQTT 的 QoS 1 和 QoS 2 消息機制。在本文中,我們將深入研究這五個報文的組成。
報文示例
我們使用 MQTTX CLI 向 公共 MQTT 服務器 發佈三條不同 QoS 等級的消息,並使用 Wireshark 工具抓取在客戶端與服務器之間往返的 MQTT 報文,Linux 環境可以使用 tcpdump 命令抓取報文,然後導入至 Wireshark 分析。
以下是本示例使用的 MQTTX CLI 命令,爲了展示 PUBLISH 報文的屬性字段,命令中還設置了 Message Expiry Interval 和 Response Topic 屬性:
mqttx pub --hostname broker.emqx.io --mqtt-version 5 \ --topic request --qos 0 --message "This is a QoS 0 message" \ --message-expiry-interval 300 --response-topic response
以下是 Wireshark 抓取到的 MQTTX CLI 發出的 QoS 爲 0 的 PUBLISH 報文:
30 31 00 07 72 65 71 75 65 73 74 10 02 00 00 01 2c 08 00 08 72 65 73 70 6f 6e 73 65 54 68 69 73 20 69 73 20 61 20 51 6f 53 20 30 20 6d 65 73 73 61 67 65
這串十六進制字節,對應以下報文內容:
而當我們僅修改 MQTTX CLI 命令中的 QoS 選項,將消息的 QoS 等級設置爲 1,我們將看到服務端在收到 PUBLISH 後回覆了 PUBACK 報文,他們的報文數據分別爲:
Client -- PUBLISH (32 33 00 .. ..) -> Server
Client <- PUBACK (40 04 64 4a 10 00) -- Server
此時 PUBLISH 報文中第一個字節從 0x30 變成了 0x32,表示這是一條 QoS 1 消息。
PUBACK 的報文結構比較簡單,可以看到 Reason Code 爲 0x10
,表示消息被接收,但是沒有匹配的訂閱者。一旦有人訂閱了 request
主題,那麼 PUBACK 報文中的 Reason Code 就會變成 0x00
,即消息被接收,且存在匹配的訂閱者。
繼續使用 MQTTX CLI 發佈一條 QoS 2 消息,我們將看到客戶端和服務端之間發生了兩次報文往返,Wireshark 會告訴我們,這些報文分別是 PUBLISH、PUBREC、PUBREL 以及 PUBCOMP,並且它們擁有相同的報文標識符 0x11c2
:
Client -- PUBLISH (34 33 00 .. ..) -> Server
Client <- PUBREC (50 04 11 c2 10 00) -- Server
Client -- PUBREL (62 03 11 c2 00) -> Server
Client <- PUBCOMP (70 04 11 c2 00 00) -- Server
如何從由十六進制字節組成的報文數據中準確地知道這是否是一個 PUBLISH 報文,它的 QoS 是多少,它的響應報文中的原因碼又是多少,接下來對這些報文的介紹將會回答這些問題。
PUBLISH 報文結構
固定報頭
PUBLISH 報文的固定報頭中,首字節的高 4 位的值固定爲 3(0b0011),低 4 位則由以下三個字段組成:
- DUP(Bit 3):客戶端或服務端在重傳 PUBLISH 報文時,需要將 DUP 標誌設置爲 1,表示這是一個重傳的報文。收到 DUP 爲 1 的 PUBLISH 報文的數量和頻率可以爲我們揭示當前通信鏈路的質量。
- QoS(Bit 2 - 1):用於指定消息的 QoS 等級。
- Retain(Bit 0):設置爲 1,表示當前消息是 保留消息;設置爲 0,則表示當前消息是普通的消息。
緊隨其後的是剩餘長度(Remaining Length)字段,指示了當前報文剩餘部分的字節數。
可變報頭
PUBLISH 報文的可變報頭按順序包含以下字段:
- 主題名(Topic Name):這是一個 UTF-8 編碼的字符串,用來指示消息應該被髮布到哪一個信息通道。
- 報文標識符(Packet Identifier):這是一個兩個字節長度的無符號整數,用來唯一地標識當前正在傳輸的消息,只有在 QoS 等級爲 1 或 2 時,報文標識符纔會出現在 PUBLISH 報文中。
- 屬性(Properties):下表列出了 PUBLISH 報文的所有可用屬性,這裏我們不再額外花費篇幅具體介紹每個屬性的用途,你可以點擊屬性名以查看對應的博客:
Identifier | Property Name | Type |
---|---|---|
0x01 | Payload Format Indicator | 單字節 |
0x02 | Message Expiry Interval | 四字節整數 |
0x23 | Topic Alias | 雙字節整數 |
0x08 | Response Topic | UTF-8 編碼的字符串 |
0x09 | Correlation Data | 二進制數據 |
0x26 | User Property | UTF-8 字符串對 |
0x0B | Subscription Identifier | 變長字節整數 |
0x03 | Content Type | UTF-8 編碼的字符串 |
有效載荷
我們發送的應用消息的實際內容,就存放在 PUBLISH 報文的有效載荷中,它可以承載任意格式的應用消息,比如 JSON、ProtoBuf 等等。
PUBACK 報文結構
固定報頭
固定報頭中首字節的高 4 位的值固定爲 4(0b0100),表示這是一個 PUBACK 報文,低 4 位是保留位,固定全部爲 0。
緊隨其後的是剩餘長度(Remaining Length)字段,指示了當前報文剩餘部分的字節數。
可變報頭
PUBACK 報文的可變報頭按順序包含以下字段:
- 報文標識符(Packet Identifier):與 PUBLISH 報文不同,PUBACK 報文中的報文標識符必須存在,它用於向對端指示這是對哪一個 QoS 爲 1 的 PUBLISH 報文的響應。
- 原因碼(Reason Code):這是一個單字節的無符號整數,用於向 PUBLISH 報文的發佈端指示發佈結果,比如是否因爲未授權而被拒絕發佈。下表列出了PUBACK 報文所有可用的 Reason Code:
Value | Reason Code Name | Description |
---|---|---|
0x00 | Success | 消息被接受。 |
0x10 | No matching subscribers | 消息被接受,但是當前沒有匹配的訂閱者。 |
0x80 | Unspecified error | 表示未指明的錯誤。當一方不希望向另一方透露錯誤的具體原因,或者協議規範中沒有能夠匹配當前情況的 Reason Code 時,那麼它將在報文中使用這個 Reason Code。 |
0x83 | Implementation specific error | PUBLISH 報文有效,但是不被當前接收方的實現所接受。 |
0x87 | Not authorized | PUBLISH 報文沒有通過服務端的權限檢查,可能是因爲當前客戶端不具備向對應主題發佈消息的權限。 |
0x90 | Topic Name invalid | 主題名的格式正確,但是不被接收端接受。 |
0x91 | Packet identifier in use | PUBLISH 報文中的 Packet ID 正在被使用,這通常意味着客戶端和服務端的會話狀態不匹配,或者有一方的實現不正確。 |
0x97 | Quota exceeded | 表示超出了配額限制。服務端可能會對發佈端的發送配額進行限制,比如每天最多爲其轉發 1000 條消息。當發佈端的配額耗盡,服務端就會在 PUBACK 等確認報文中使用這個 Reason Code 提醒對方。 |
0x99 | Payload format invalid | 表示有效載荷的格式與 Payload Format Indicator 屬性所指示的格式不匹配。 |
- 屬性(Properties):下表列出了 PUBACK 報文的所有可用屬性。
Identifier | Property Name | Type |
---|---|---|
0x1F | Reason String | UTF-8 編碼的字符串 |
0x26 | User Property | UTF-8 字符串對 |
有效載荷
PUBACK 報文不包含有效載荷。
PUBREC、PUBREL、PUBCOMP 報文結構
PUBREC、PUBREL 和 PUBCOMP 的報文結構與 PUBACK 基本一致,它們的區別主要在於固定報頭中報文類型字段的值,以及可以使用的原因碼。
報文類型字段的值爲 5(0b0101),表示這是一個 PUBREC 報文;值爲 6(0b0110),則表示這是一個 PUBREL報文;值爲 7(0b0111),則表示這是一個 PUBCOMP 報文。
PUBREC 作爲 QoS 2 消息流程中對 PUBLISH 報文的確認報文,它可以使用的原因碼與 PUBACK 完全一致。PUBREL 和 PUBCOMP 報文可用的原因碼如下:
Identifier | Reason Code Name | Description |
---|---|---|
0x00 | Success | 由 QoS 2 消息的發送端在 PUBREL 報文中返回時,表示消息已經被釋放,即之後將不會再重傳該消息。由 QoS 2 消息的接收端在 PUBREC 報文中返回時,表示消息中使用的報文標識符已經釋放,現在發送端可以使用該報文標識符發送新的消息。 |
0x92 | Packet Identifier not found | 表示收到了未知的報文標識符,這通常意味着當前服務端和客戶端的會話狀態不匹配。 |
總結
PUBLISH 報文中的主題決定了消息的流向,QoS 則決定了消息的可靠性,同時也決定了傳輸時將用到哪些報文,PUBACK 報文用於 QoS 1 消息,PUBREC、PUBREC 和 PUBCOMP 報文用於 QoS 2 消息。QoS 大於 0 時報文中還需要包含報文標識符來關聯 PUBLISH 報文和它的響應報文。
PUBLISH 報文的有效載荷不限制數據類型,所以我們可以傳輸任意格式的應用消息。另外,屬性可以滿足我們在更多場景下的需要,比如主題別名可以減少每個消息的大小,消息過期間隔可以爲有時效性的消息設置過期時間等等。
PUBLISH 報文的響應報文除了向發送端指示消息被接收以外,還能通過 Reason Code 進一步指示發佈結果。所以當訂閱端遲遲無法收到消息,我們還可以通過發佈端收到的響應報文中的原因碼來排查問題。
以上就是對 MQTT PUBLISH 及其響應報文的介紹,在下一篇文章中,我們將繼續研究訂閱和取消訂閱報文的結構和組成。