MQTT協議數據怎么流轉
時間:2023-09-11 來源:華清遠見
1、什么是MQTT?
MQTT(Message Queuing Telemetry Transport,消息隊列遙測傳輸協議),是一種基于發布/訂閱(Publish/Subscribe)模式的輕量級通訊協議,該協議構建于TCP/IP協議上。MQTT最大的優點在于可以以極少的代碼和有限的帶寬,為遠程設備提供實時可靠的消息服務。做為一種低開銷、低帶寬占用的即時通訊協議,MQTT在物聯網、小型設備、移動應用等方面有廣泛的應用。
2、MQTT通信機制
一張圖看懂MQTT通信機制,如下圖:
首先要明白發布者、訂閱者和代理之間的關系,發布者和訂閱者作為客戶端(一個客戶端可以同時成為發布者和訂閱者),代理作為服務器(通常都是對接云服務,比如阿里云服務)。客戶端先要通過TCP/IP協議和服務器建立連接,然后雙方才可以進行MQTT通信。通信消息類型有連接與應答、發布與應答、心跳與應答、訂閱和取消訂閱、關閉等類型。具體的可以看第4節MQTT協議數據傳輸格式。

3、數據表示
由于數據網絡傳輸用的是大端序的(big-endian,高位字節在低位字節前面),數據格式和嵌入式編程有所不同(一般都是小端序),進行封包時要有格式轉換,具體請看下邊。
3.1、單字節數據
字節中的位從0到7。第7位是最高有效位,第0位是最低有效位。
3.2、雙字節整數
雙字節整數數值是使用大端序(big-endian,高位字節在低位字節前面)的16位無符號整數。這意味著一個16位的字在網絡上表示為最高有效位(MSB),后面跟著最低有效位(LSB)。
3.3、四字節整數(MQTT5.0新增)
四字節整數數據值是按大端序(big-endian,高位字節在低位字節前面)的32位無符號整數:高位字節先于連續的低位字節。 這意味著一個32位字在網絡上顯示為最高有效字節(MSB),然后是下一個最高有效字節(MSB),然后是下一個最高有效字節(MSB),然后是最低有效字節(LSB)。
3.4、UTF-8編碼字符串
后續描述的MQTT控制報文中的文本字段編碼為 UTF-8 格式的字符串。UTF-8 是Unicode字符的有效編碼,優化ASCII字符的編碼以支持基于文本的通信。
除非另有說明,否則所有UTF-8編碼的字符串長度都可以在0到65535字節之間。
3.5、UTF-8字符串鍵值對(MQTT5.0新增)
UTF-8字符串對由兩個UTF-8編碼的字符串組成,用來表示名字-值對,第一個字符串表示名字,第二個字符串表示值。
所有的字符串必須遵循UTF-8字符串編碼規范,如果接受者(客戶端或者服務端)接受到一個字符串對,然而其編碼并不遵循規范,則此報文為無效報文。
4、MQTT協議數據傳輸格式
總體結構分為3個部分:固定報頭、可變報頭和有效載荷。

4.1、固定報頭:
最少2個字節,第1個字節高4位是報文類型,低四位從高到低分別對應為DUP(重發標志占1bit)、QOS(服務等級占2個bit)、RETAIN(保留標志占1個bit)。第2個字節開始表示的是可變報頭和有效載荷(數據)的長度(所占字節長度非固定字節數,最多4個字節)。
4.1.1、第一個字節:
報文類型:bit7~bit4
標志位:bit3~bit0

4.1.1.1、報文類型:


4.1.1.2、標志位:


4.1.1.3、DUP重發標志位:
只有在QOS>1時,才有可能用到。當第一次發布消息時,置0,如果是重復發布的要置1。
4.1.1.4、QOS服務等級:
QOS = 0時,“最多一次”,盡操作環境所能提供的最大努力分發消息。消息可能會丟失。例如,這個等級可用于環境傳感器數據,單次的數據丟失沒關系,因為不久之后會再次發送。
QOS = 1時,“至少一次”,保證消息可以到達,但是可能會重復。
QOS = 2時,“僅一次”,保證消息只到達一次。例如,這個等級可用在一個計費系統中,這里如果消息重復或丟失會導致不正確的收費。
4.1.1.5、RETAIN保留標志位:
當置1時,服務端將保留此條應用消息,分發給未來的訂閱者。如果載荷為空,相當于清空消息。
4.1.2、第二個字節:(msg_len)
可變報頭長度加上有效載荷長度,編碼為變長字節整數。此長度的所占字節數要根據字節的最高位來決定,如果當前字節的最高位置1,說明下一個字節還表示長度,表示長度最多占4個字節,所以長度的最后一個字節最高為必須是0,每個字節表示最大值是128(十進制),也就是說每一包數據最大長度是128*128*128*128。

4.2、可變報頭報文類型:
可變報頭是根據報文類型和標志位變化,所以對每個不同的報文類型做解析。報文類型分為以下6類:
連接與響應、發布與響應、訂閱與取消訂閱、Ping(心跳)、關閉、認證交換(5.0支持)。
4.2.1、連接與響應:
4.2.1.1、CONNECT類型(5.0):
可變報頭的順序是:協議名(Protocol Name),協議級別(Protocol Level),連接標志(Connect Flags),保持連接(Keep Alive)和屬性(Properties),客戶端標識符,遺囑主題,遺囑消息,用戶名,密碼。
協議名(Protocol Name):UTF-8格式字符串。
協議級別(Protocol Level):協議版本號。占1個字節。
連接標志位(Connect Flags):
用戶名標志 User Name Flag:置1表明后面有用戶名字段,占1個Bit。
密碼標志 Password Flag:置1名后面有密碼字段,占1個Bit。
遺囑保留標志 Will Retain:在遺囑標志為1時才有效,否則錯誤。置1,發送遺囑后是否保留信息,占1個Bit。
遺囑服務質量 Will QoS:在遺囑標志為1時才有效,否則錯誤。遺囑的服務等級,占2個Bit。
遺囑標志 Will Flag:置1表明后面字段有遺囑主題和遺囑消息,其內容是在正常關閉后發布,占1個Bit。
新開始 Clean Start:置1為新連接。占1個Bit。
保留 Reserved:此位置1說明報文錯誤,占1個Bit。
保持連接(Keep Alive):
客戶端和服務端保持連接的心跳時間,占2個字節的時間值,單位為秒。如果服務端在回復CONNACK中有配置間隔時間,客戶端還需另配置此值。允許的最大值是18小時12分15秒。
屬性(Properties):(5.0新增)
屬性第一個字節表示可變長度,同第二個字節(msg_len)。
屬性標識符數值及數據類型對照表如下:


4.2.1.1.1、可變報頭示例:



4.2.1.1.2、有效載荷:(以CONNECT為例)
客戶端標識符(Client Identifier):UTF-8 編碼字符串。
當連接標志位遺囑標志 Will Flag置1時有效:
遺囑屬性長度(Property Length):屬性長度被編碼為可變長字節整數。(5.0新增)。
遺囑屬性標識符及字段():查詢屬性(5.0新增)。
遺囑主題(Will Topic):UTF-8編碼的字符串。
遺囑荷載(Will Payload):這個字段由一個兩字節的長度和遺囑消息的有效載荷組成,此字段為二進制數據。
用戶名:UTF-8 編碼字符串 。
密碼:UTF-8 編碼字符串 。

4.2.1.2、CONNACK類型:確認連接請求
連接確認標志(Connect Acknowledge Flags):
第1個字節是連接確認標志,位7-1是保留位且必須設置為0 ,第0(SP)位是會話存在標志(Session Present Flag)。
此標志位置1,且連接原因碼成功(0x00),說明服務端已經保存了客戶端ID。
連接原因碼(Reason Code):
第2個字節是連接原因碼。占用1個字節。詳情看下方連接原因碼 Connect Reason Code。
屬性(Properties):
長度上邊參考屬性標識符數值及數據類型對照表。
連接原因碼 Connect Reason Code:


4.2.2、發布與響應
4.2.2.1、PUBLISH類型:發布
固定報頭:
解析字節長度方法同上。
可變報頭:
主題名(Topic Name):UTF-8編碼的字符串 。
報文標識符(Packet Identifier):消息ID。
只有當QoS等級是1或2時,報文標識符(Packet Identifier)字段才能出現在PUBLISH報文中。
屬性(Properties):
長度上邊參考屬性標識符數值及數據類型對照表。
可變報頭示例:

有效載荷:
載荷包含將被發布的應用消息。載荷的內容和格式由應用程序指定。有效載荷的長度這樣計算:用固定報頭中的剩余長度字段的值減去可變報頭的長度。包含零長度有效載荷的PUBLISH報文是合法的。
服務端在接收到客戶端的PUBACK,PUBCOMP或包含原因碼大于等于128的PUBREC報文之前,不能發送數量超過客戶端的接收最大值(Receive Maximum)的QoS為1和2的PUBLISH報文。客戶端在發送PUBACK或PUBCOMP響應之前,如果收到數量超過服務端的接收最大值的QoS為1和2的PUBLISH報文,客戶端使用包含原因碼為0x93(超出接收最大值)的DISCONNECT報文斷開網絡連接。
4.2.2.2、PUBACK類型、PUBREC類型、PUBREL類型、PUBCOMP類型:回應
都是收到消息回應,PUBACK是對QoS1回應,PUBREC是QoS2模式下對發布方回應(第一個回應報文),PUBREL是對QoS2模式下收到PUBREC后發布方再次發給接受方(第二個回應報文),PUBCOMP是QoS2模式下對發布方最后回應(第三個回應報文)。
固定報頭:
解析字節長度方法同上。
可變報頭:
所確認的PUBLISH報文標識符:報文標識符(Packet Identifier)。
PUBACK原因碼:詳情看上方連接原因碼 Connect Reason Code。
屬性(Properties):長度上邊參考屬性標識符數值及數據類型對照表。
屬性的第1個字節是可變報頭中的屬性長度被編碼為變長字節整數。為0x00則沒有屬性。
... ...
有效載荷:
沒有有效載荷。
4.2.3、訂閱與取消訂閱
4.2.3.1、SUBSCRIBE類型:訂閱請求
客戶端向服務端發送SUBSCRIBE報文用于創建一個或多個訂閱。每個訂閱(Subscription)注冊客戶端所感興趣的一個或多個主題。服務端向客戶端發送PUBLISH報文以轉發被發布到符合這些訂閱主題的應用消息。SUBSCRIBE報文同樣(為每個訂閱)指定了服務端可以向其發送的應用消息最大QoS等級。
固定報頭:
解析字節長度方法同上。SUBSCRIBE報文固定報頭第1個字節的第3,2,1,0比特位是保留位,必須被設置為0,0,1,0。(QoS1)服務端必須將其他的任何值都當做是不合法的并關閉網絡連接。
可變報頭:
報文標識符(Packet Identifier):消息ID。
屬性(Properties):
屬性的第1個字節是可變報頭中的屬性長度被編碼為變長字節整數。為0x00則沒有屬性。
... ...
有效載荷:
載荷包含一列主題過濾器,指明客戶端希望訂閱的主題。主題過濾器必須為UTF-8 編碼的字符串 。每個主題過濾器之后跟著一個訂閱選項(Subscription Options)字節。載荷必須包含至少一個主題過濾器/訂閱選項對。不包含載荷的SUBSCRIBE報文將造成協議錯誤(Protocol Error)。
主題過濾器:
UTF-8 編碼的字符串。
訂閱選項:占1個字節
第0和1比特代表最大服務質量字段。此字段給出服務端可以向此客戶端發送的應用消息的最大QoS等級。最大服務質量字段為3將造成協議錯誤(Protocol Error)。
第2比特表示非本地(No Local)選項。值為1,表示應用消息不能被轉發給發布此消息的客戶標識符 。共享訂閱時把非本地選項設為1將造成協議錯誤(Protocol Error)。
第3比特表示發布保留(Retain As Published)選項。值為1,表示向此訂閱轉發應用消息時保持消息被發布時設置的保留(RETAIN)標志。值為0,表示向此訂閱轉發應用消息時把保留標志設置為0。當訂閱建立之后,發送保留消息時保留標志設置為1。
第4和5比特表示保留操作(Retain Handling)選項。此選項指示當訂閱建立時,是否發送保留消息。此選項不影響之后的任何保留消息的發送。如果沒有匹配主題過濾器的保留消息,則此選項所有值的行為都一樣。值可以設置為:0 = 訂閱建立時發送保留消息1 = 訂閱建立時,若該訂閱當前不存在則發送保留消息2 = 訂閱建立時不要發送保留消息保留操作的值設置為3將造成協議錯誤(Protocol Error)。
第6和7比特為將來所保留。服務端必須把此保留位非0的SUBSCRIBE報文當做無效報文。
SUBSCRIBE類型的有效載荷結構:

RAP指發布保留(Retain as Published)。NL指非本地(No Local)。
4.2.3.2、SUBACK類型:訂閱確認
服務端發送SUBACK報文給客戶端,用于確認它已收到并且正在處理SUBSCRIBE報文。SUBACK報文包含一個原因碼列表,用于指定授予的最大QoS等級或SUBSCRIBE報文所請求的每個訂閱發生的錯誤。
固定報頭:
解析字節長度方法同上。
可變報頭:
所確認的SUBSCRIBE報文標識符:消息ID。
屬性(Properties):
屬性的第1個字節是SUBACK可變報頭中的屬性長度被編碼為變長字節整數。為0x00則沒有屬性。
... ...
有效載荷:
有效載荷包含一個原因碼列表。每個原因碼對應SUBSCRIBE報文中的一個被確認的主題過濾器。SUBACK報文中的原因碼順序必須與SUBSCRIBE報文中的主題過濾器順序相匹配。
訂閱原因碼 Subscribe Reason Codes:


4.2.3.3、UNSUBSCRIBE類型:取消訂閱請求
客戶端發送UNSUBSCRIBE報文給服務端,用于取消訂閱主題。
固定報頭:
解析字節長度方法同上。UNSUBSCRIBE固定報頭的第3,2,1,0位是保留位且必須分別設置為0,0,1,0。服務端必須認為任何其它的值都是不合法的并關閉網絡連接。
可變報頭:
報文標識符:消息ID。
屬性(Properties):
屬性的第1個字節是可變報頭中的屬性長度被編碼為變長字節整數。為0x00則沒有屬性。
... ...
有效載荷:
UNSUBSCRIBE報文有效載荷包含一列客戶端希望取消訂閱的主題過濾器。UNSUBSCRIBE報文中的主題過濾器必須為UTF-8編碼字符串 ,且連續填充。UNSUBSCRIBE報文有效載荷必須包含至少一個主題過濾器 。不包含有效載荷的UNSUBSCRIBE報文將造成協議錯誤(Protocol Error)。
UNSUBSCRIBE報文有效載荷示例:


4.2.3.4、UNSUBACK類型:取消訂閱確認
服務端發送UNSUBACK報文給客戶端用于確認收到UNSUBSCRIBE報文。
固定報頭:
解析字節長度方法同上。
表示可變報頭的長度,對UNSUBACK報文這個值等于2。
可變報頭:
所確認的UNSUBSCRIBE報文標識符:消息ID。
屬性( Properties):
屬性的第1個字節是可變報頭中的屬性長度被編碼為變長字節整數。為0x00則沒有屬性。
... ...
有效載荷:
有效載荷包含一個原因碼列表。每個原因碼對應UNSUBSCRIBE報文中的一個被確認的主題過濾器。UNSUBACK報文中的原因碼順序必須與UNSUBSCRIBE報文中的主題過濾器順序相匹配 。單字節無符號取消訂閱原因碼的值如下所示。服務端發送UNSUBACK報文時對于每個收到的主題過濾器,必須使用一個取消訂閱原因碼。
UNSUBACK原因碼:


4.2.4、Ping(心跳)
4.2.4.1、PINGREQ類型:ping請求
客戶端發送PINGREQ報文給服務端,可被用于:
在沒有任何其他MQTT控制報文從客戶端發給服務端時,告知服務端客戶端還活著。
請求服務端發送響應以確認服務端還活著。
使用網絡已確認網絡連接沒有斷開。
此報文被用在保持連接(Keep Alive)的處理中。
只有2個字節的固定報頭:0xC0,0x00。沒有可變報頭和有效載荷。
4.2.4.2、PINGRESP類型:心跳響應
服務端發送PINGRESP報文響應客戶端的PINGREQ報文。表示服務端還活著。
保持連接(Keep Alive)處理中用到這個報文。
只有2個字節的固定報頭:0xD0,0x00。沒有可變報頭和有效載荷。
4.2.5、關閉
DISCONNECT類型:
DISCONNECT報文是客戶端發給服務端的最后一個MQTT控制報文。表示客戶端為什么斷開網絡連接的原因。客戶端和服務端在關閉網絡連接之前可以發送一個DISCONNECT報文。如果在客戶端沒有首先發送包含原因碼為0x00(正常斷開)DISCONNECT報文并且連接包含遺囑消息的情況下,遺囑消息會被發布。
固定包頭:
服務端或客戶端必須驗證所有的保留位都被設置為0,如果他們不為0,發送包含原因碼為0x81(無效報文)的DISCONNECT報文。
可變報頭:
斷開原因碼:
占1個字節,看下面斷開原因值。
屬性(Properties):
屬性的第1個字節是可變報頭中的屬性長度被編碼為變長字節整數。為0x00則沒有屬性。
... ...
有效載荷:
DISCONNECT報文沒有有效載荷。
斷開原因值 Disconnect Reason Code:


4.2.6、AUTH類型:認證交換
AUTH報文固定報頭第3,2,1,0位是保留位,必須全設置為0。客戶端或服務端必須把其他值當做無效值并關閉網絡連接 。
固定報頭:
可變報頭:如果原因碼為0x00(成功)并且沒有屬性字段,則可以省略原因碼和屬性長度。這種情況下,AUTH固定報文剩余長度為0。
認證原因碼(Authentication Reason Code):
1個字節,詳情看下面認證原因碼。
屬性(Properties):
屬性的第1個字節是可變報頭中的屬性長度被編碼為變長字節整數。為0x00則沒有屬性。
... ...
有效載荷:
AUTH報文沒有有效載荷。
認證原因碼 Authenticate Reason Codes:

5、舉例:WireShark抓MQTT包分析
5.1、Connect 包

5.2、Connect Ack 包

5.3、Publish Message 包

5.4、Publish Ack 包


