MQTT 5.0 報文(Packets)入門指南

什麼是 MQTT 控制報文?

MQTT 控制報文是 MQTT 數據傳輸的最小單元。MQTT 客戶端和服務端通過交換控制報文來完成它們的工作,比如訂閱主題和發佈消息。

MQTT 目前定義了 15 種控制報文類型,如果按照功能進行分類,我們可以將這些報文分爲連接、發佈、訂閱三個類別:

01mqttpackettypes.png

其中,CONNECT 報文用於客戶端向服務端發起連接,CONNACK 報文則作爲響應返回連接的結果。如果想要結束通信,或者遇到了一個必須終止連接的錯誤,客戶端和服務端可以發送一個 DISCONNECT 報文然後關閉網絡連接。

AUTH 報文是 MQTT 5.0 引入的全新的報文類型,它僅用於增強認證,爲客戶端和服務端提供更安全的身份驗證。

PINGREQ 和 PINGRESP 報文用於連接保活和探活,客戶端定期發出 PINGREQ 報文向服務端表示自己仍然活躍,然後根據 PINGRESP 報文是否及時返回判斷服務端是否活躍。

PUBLISH 報文用於發佈消息,餘下的四個報文分別用於 QoS 1 和 2 消息的確認流程。

SUBSCRIBE 報文用於客戶端向服務端發起訂閱,UNSUBSCRIBE 報文則正好相反,SUBACK 和 UNSUBACK 報文分別用於返回訂閱和取消訂閱的結果。

MQTT 報文格式

MQTT 中,無論是什麼類型的控制報文,它們都由固定報頭、可變報頭和有效載荷三個部分組成。

固定報頭固定存在於所有控制報文中,而可變報頭和有效載荷是否存在以及它們的內容則取決於具體的報文類型。例如用於維持連接的 PINGREQ 報文就只有一個固定報頭,用於傳遞應用消息的 PUBLISH 報文則完整地包含了這三個部分。

02packetformat.png

固定報頭

固定報頭由報文類型、標識位和報文剩餘長度三個字段組成。

03fixedheader.png

報文類型位於固定報頭第一個字節的高 4 位,它是一個無符號整數,很顯然,它表示當前報文的類型,例如 1 表示這是一個 CONNECT 報文,2 表示 CONNACK 報文等等。詳細的映射關係可以參閱 MQTT 5.0 規範 - MQTT 控制報文類型。事實上,除了報文類型和剩餘長度這兩個字段,MQTT 報文剩餘部分的內容基本都取決於具體的報文類型,所以這個字段也決定了接收方應該如何解析報文的後續內容。

固定報頭第一個字節中剩下的低 4 位包含了由控制報文類型決定的標識位。不過到 MQTT 5.0 爲止,只有 PUBLISH 報文的這四個比特位被賦予了明確的含義:

  • Bit 3:DUP,表示當前 PUBLISH 報文是否是一個重傳的報文。
  • Bit 2,1:QoS,表示當前 PUBLISH 報文使用的服務質量等級。
  • Bit 0:Retain,表示當前 PUBLISH 報文是否是一個保留消息。

其他所有的報文中,這 4 位都仍是保留的,即它們是一個固定的,不可隨意變更的值。

最後的剩餘長度指示了當前控制報文剩餘部分的字節數,也就是可變報頭和有效載荷這兩個部分的長度。所以 MQTT 控制報文的總長度實際上等於固定報頭的長度加上剩餘長度。

04remaininglength.png

可變字節整數

但固定報頭長度並不是固定的,爲了儘可能地減少報文大小,MQTT 將剩餘長度字段設計成了一個可變字節整數。

在 MQTT 中,存在很多長度不確定的字段,例如 PUBLISH 報文中的 Payload 部分就用來承載實際的應用消息內容,而應用消息的長度顯然是不固定的。所以我們需要一個額外的字段來指示這些不定長內容的長度,以便接收端正確地解析。

一個 2 兆大小,也就是總共 2,097,152 個字節的應用消息,我們就需要一個 4 字節長度的整數才能夠指示它的長度。但並不是所有的應用消息都有這麼大,更多情況下是幾 KB 甚至幾個字節。用一個 4 字節長度的整數來指示一個總共 2 個字節長度的應用消息,顯然是過於浪費了。

所以 MQTT 的可變字節整數就被設計出來了,它將每個字節中的低 7 位用於編碼數據,最高的有效位用於指示是否還有更多的字節。這樣,長度小於 128 字節時可變字節整數只需要一個字節就可以指示。可變字節整數的最大長度爲 4 個字節,所以最多可以指示長度爲 (2^28 - 1) 字節,也就是 256 MB 的數據。

05variablebyteinteger.png

可變報頭

可變報頭的內容取決於具體的報文類型。例如 CONNECT 報文的可變報頭按順序包含了協議名、協議級別、連接標識、Keep Alive 和屬性這五個字段。PUBLISH 報文的可變報頭則按順序包含了主題名、報文標識符和屬性這三個字段。

06variableheader.png

需要注意這裏提到的順序,可變報頭中字段出現的順序必須嚴格遵循協議規範,因爲接收端只會按照協議規定的字段順序進行解析。我們也不能隨意地遺漏某個字段,除非是協議明確要求或允許的。例如,在 CONNECT 報文的可變報頭中,如果協議名之後直接就是連接標識,那麼就會導致報文解析失敗。而在 PUBLISH 報文的可變報頭中,報文標識符就只有在 QoS 不爲 0 的時候才能存在。

屬性

屬性是 MQTT 5.0 引入的一個概念。屬性字段基本上都是可變報頭的最後一部分,由屬性長度和緊隨其後的一組屬性組成,這裏的屬性長度指的是後面所有屬性的總長度。

07propertiesinvariableheader.png

所有的屬性都是可選的,因爲它們通常都有一個默認值,如果沒有任何屬性,那麼屬性長度的值就爲 0。

每個屬性都由一個定義了屬性用途和數據類型的標識符和具體的值組成。不同屬性的數據類型可能不同,比如一個是雙字節長度的整數,另一個則是 UTF-8 編碼的字符串,所以我們需要按照標識符所聲明的數據類型對屬性進行解析。

08property.png

屬性之間的順序可以是任意的,這是因爲我們可以根據標識符知道這是哪個屬性,以及它的長度是多少。

屬性通常都是爲了某個專門的用途而設計的,比如在 CONNECT 報文中就有一個用於設置會話過期時間的的 Session Expiry Interval 屬性,但顯然我們在 PUBLISH 報文中就不需要這個屬性。所以 MQTT 也嚴格定義了屬性的使用範圍,一個合法的 MQTT 控制報文中不應該包含不屬於它的屬性。

包含標識符、屬性名、數據類型和使用範圍的完整 MQTT 屬性列表,請參閱 MQTT 5.0 Specification - Properties

有效載荷

最後是有效載荷部分。我們可以將報文的可變報頭看作是它的附加項,而有效載荷則用於實現這個報文的核心目的。

比如在 PUBLISH 報文中,Payload 用於承載具體的應用消息內容,這也是 PUBLISH 報文最核心的功能。而 PUBLISH 報文的可變報頭中的 QoS、Retain 等字段,則是圍繞着應用消息提供一些額外的能力。

SUBSCRIBE 報文也是如此,Payload 包含了想要訂閱的主題以及對應的訂閱選項,這也是 SUBSCRIBE 報文最主要的工作。

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