MQTT 5.0 報文解析 02:PUBLISH 與 PUBACK

歡迎閱讀 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

這串十六進制字節,對應以下報文內容:

01publishpacket.png

而當我們僅修改 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,即消息被接收,且存在匹配的訂閱者。

02pubackpacket.png

繼續使用 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 報文結構

可變報頭

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 報文結構

可變報頭

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 及其響應報文的介紹,在下一篇文章中,我們將繼續研究訂閱和取消訂閱報文的結構和組成。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章