MQTT協議 -- 消息報文格式

雖然學習協議是枯燥的,可是熟悉協議自己倒是很重要的事情。若是能把其細節弄清楚,而且配合一些實驗來學習,就不會那麼枯燥了。html

消息報文格式

MQTT協議是應用層協議,須要藉助TCP/IP協議進行傳輸,相似HTTP協議。MQTT協議也有本身的格式,以下表:git

[ Fixed Header | Variable Header | Payload]github

Fixed Header: 固定頭部,MQTT協議分不少種類型,如鏈接,發佈,訂閱,心跳等。其中固定頭是必須的,全部類型的MQTT協議中,都必須包含固定頭。shell

**Variable Header:**可變頭部,可變頭部不是可選的意思,而是指這部分在有些協議類型中存在,在有些協議中不存在。bash

**Payload:**消息載體,就是消息內容。與可變頭同樣,在有些協議類型中有消息內容,有些協議類型中沒有消息內容。服務器


固定頭

固定頭包含兩部份內容,首字節(字節1)和剩餘消息報文長度(1-4字節)。tcp

Bit 7 6 5 4 3 2 1 0
Byte 1 MQTT Control Packet type Flags specific to each MQTT Control Packet type
Byte 2... Remaining Length

爲了不翻譯不許確,這裏都使用官方的原始術語。其中MQTT Control Packet type能夠簡單理解爲字節位Bit[7-4]用於肯定報文類型。Flags specific to each MQTT Control Packet type意思是字節位Bit[3-0]用做某些報文的特殊標記。學習

首字節

首字節用於表示MQTT消息的報文類型以及某些類型的控制標記,如上圖。高4位(bit7~bit4)表示協議類型,總共能夠表示16種協議類型,其中0000和1111是保留字段。MQTT消息報文類型以下。測試

報文類型 字段值 數據方向 描述
保留 0 禁用 保留
CONNECT 1 Client ---> Server 客戶端鏈接到服務器
CONNACK 2 Server ---> Client 鏈接確認
PUBLISH 3 Client <--> Server 發佈消息
PUBACK 4 Client <--> Server 發不確認
PUBREC 5 Client <--> Server 消息已接收(QoS2第一階段)
PUBREL 6 Client <--> Server 消息釋放(QoS2第二階段)
PUBCOMP 7 Client <--> Server 發佈結束(QoS2第三階段)
SUBSCRIBE 8 Client ---> Server 客戶端訂閱請求
SUBACK 9 Server ---> Client 服務端訂閱確認
UNSUBACRIBE 10 Client ---> Server 客戶端取消訂閱
UNSUBACK 11 Server ---> Client 服務端取消訂閱確認
PINGREQ 12 Client ---> Server 客戶端發送心跳
PINGRESP 13 Server ---> Client 服務端回覆心跳
DISCONNECT 14 Client ---> Server 客戶端斷開鏈接請求
保留 15 禁用 保留

首字節的低4位(bit3~bit0)用來表示某些報文類型的控制字段,實際上只有少數報文類型有控制位,以下圖。ui

報文類型 固定頭標記 Bit 3 Bit 2 Bit 1 Bit 0
CONNECT 保留 0 0 0 0
CONNACK 保留 0 0 0 0
PUBLISH Used in MQTT 3.1.1 DUP QoS QoS RETAIN
PUBACK 保留 0 0 0 0
PUBREC 保留 0 0 0 0
PUBREL 保留 0 0 1 0
PUBCOMP 保留 0 0 0 0
SUBSCRIBE 保留 0 0 1 0
SUBACK 保留 0 0 0 0
UNSUBACRIBE 保留 0 0 1 0
UNSUBACK 保留 0 0 0 0
PINGREQ 保留 0 0 0 0
PINGRESP 保留 0 0 0 0
DISCONNECT 保留 0 0 0 0

當發佈PUBLISH消息時,若是DUP字段(bit 3)設置爲1,代表這是一條重複消息,不然是第一次發佈消息。爲了保證消息的可靠性傳遞,當QoS設置爲1時,客戶端或服務器發佈消息時,須要獲得對方的確認(PUBACK),若是一段時間後沒收到PUBACK,那麼會再次發送當前消息,並將DUP字段標記爲1。

QoS用來代表QoS等級,若是Bit 1和Bit 2都爲0,表示QoS 0。若是Bit 1爲1,表示QoS 1。若是Bit 2爲1,表示QoS 2。若是同時將Bit 1和Bit 2都設置成1,那麼客戶端或服務器認爲這是一條非法的消息,會關閉當前鏈接。

目前Bit[3-0]只在PUBLISH協議中使用有效,而且表中指明瞭是MQTT 3.1.1版本。對於其它MQTT協議版本,內容可能不一樣。全部固定頭標記爲"保留"的協議類型,Bit[3-0]必須保持與表中保持一致,如SUBSCRIBE協議,其Bit 1必須爲1。若是接收方接收到非法的消息,會強行關閉當前鏈接。

Remaining Length

Remaining Length意思是剩餘長度,即Variable Header + Payload的長度。剩餘長度從Byte 2開始,最長可達4字節。因此剩餘長度範圍是Byte[2-5]。那麼怎樣肯定其長度究竟是1仍是4呢,這取決於字節的最高位Bit 7(默認都是高字節在前),若是這個值是1,那麼就繼續計算字節長度,若是是0,那麼就再也不計算字節長度。

消息長度能夠簡單理解爲128進制的數據,4位長度最大能夠表示128*128*128*128Byte=256MB。可是這個長度的計算有些特別,就是低位在前,高位在後(由於正常的表示方法是高位在前,低位在後),字節最高位Bit7用於標記是否須要繼續計算消息長度。如下是消息長度的長度範圍:

字節 最小值 最大值
1 0(0x00) 127(0x7F)
2 128 (0x80, 0x01) 16 383 (0xFF, 0x7F)
3 16 384 (0x80, 0x80, 0x01) 2 097 151 (0xFF, 0xFF, 0x7F)
4 2 097 152 (0x80, 0x80, 0x80, 0x01) 268 435 455 (0xFF, 0xFF, 0xFF, 0x7F)

稍微注意一下,0x80=1000 0000,不是 1000。剛開始覺得是1000,因此就沒明白。

舉個例子。

消息假設長度是[0X60],其二進制是01100000,字節最高位Bit7(從左邊起第0位)是0,因此不須要繼續日後計算。那麼消息長度就是0X60,十進制數是96。

若是消息長度是[0XC1, 0XC2, 0X33],那麼他們的二進制分別以下,

0xC1=1100 0001

0xC2=1100 0010

0x33=0011 0011,

第一字節最高位是1,那麼須要繼續向後計算,去掉標記位(0xC1%128),獲得100 0001=41

第二字節最高位是1,那麼須要繼續向後計算,去掉標記位(0xC2%128),獲得100 0010=42

第三字節最高位是0,不須要向後計算,其結果就是0x33=51

由於低位在前,高位在後,那麼長度計算爲Length=41 + 42*128 + 51*128*128=841001 B = 821KB

須要注意的是,消息長度=可變頭部長度+消息內容長度。不包括首字節和消息長度自己,若是消息長度爲5,那麼說明這條消息後邊還有5字節,整條消息長度爲7(首字節+1位長度字節+5)。

另外若是消息長度爲4字節,最後一位不能超過0X7F=127,由於若是超出這個值,其最高位Bit7是1,還須要日後計算,這與消息最大長度爲4字節矛盾。因此若是出現[0XFF, 0XFF, 0XFF, 0XFF]這樣的消息長度,那麼接收方認爲這是一條非法的消息。


Variable Header

Variable Header的意思是可變化的消息頭部。有些報文類型包含可變頭部,如PUBLISH,SUBSCRIBE,CONNECT等等。可變頭部在固定頭部和消息內容之間,其內容根據報文類型不一樣而不一樣。

Packet Identifier(消息ID)是一種常見的可變頭部,一個消息ID包含2字節,高字節在前,低字節在後。包含Packet Identifier的協議類型包括:

報文類型 包含可變頭
PUBLISH YES(QoS > 0)
PUBACK YES
PUBREC YES
PUBREL YES
PUBCOMP YES
SUBSCRIBE YES
SUBACK YES
UNSUBSCRIBE YES
UNSUBACK YES

消息ID默認是從1開始並自增,若是一個消息ID被用完後,這個消息ID能夠被重用。對於PUBLISH (QoS 1)來講,若是發送端接收到PUBACK,那麼這個消息ID就用完了。對於PUBLISH(QoS 2),若是接收方收到PUBCOMP,那麼這個消息ID就用完了。對於SUBSCRIBE和UNSUBSCRIBE,消息ID使用完成的標記是發送方收到了對應的SUBACK和UNSUBACK。

另外客戶端和服務端的消息ID是獨立分配的,客戶端和服務端能夠同時使用同一個消息ID。好比

Client                     Server

   PUBLISH Packet Identifier=0x1234--->

   <--PUBLISH Packet Identifier=0x1234

   PUBACK Packet Identifier=0x1234--->

   <--PUBACK Packet Identifier=0x1234
複製代碼

上邊消息客戶端給服務端發送一條消息,使用的消息ID是0x1234,同時服務端給客戶端發送了一條消息,也使用了消息ID 0x1234。而後客戶端回覆服務端,發送PUBACK,最後是客戶端收到服務端的回覆PUBACK。

另外其它協議如CONNECT和CONNACK也有可變頭部,具體請參見MQTT-Packet CONNECT Variable Header


Payload

有些報文類型是包含Payload的,Payload意思是消息載體的意思,如PUBLISH的Payload就是指消息內容。而CONNECT的Payload則包含Client Identifier,Will Topic,Will Message,Username,Password等信息。具體請參見MQTT-Packet CONNECT Payload

包含Payload的報文類型以下:

報文類型 是否包含Payload
CONNECT YES
PUBLISH 可選
SUBSCRIBE YES
SUBACK YES
UNSUBSCRIBE YES

除了上面列出的報文類型,其它的報文類型都沒有Payload。

抓包測試

咱們使用Wire Shark抓包,來探測一下MQTT消息內容。

1. 打開Wireshark,選擇你的網卡,添加如下過濾條件,並點擊開始捕獲。

tcp.port==1883
複製代碼

2. 打開終端,輸入如下命令,發佈一條消息。若是你不理解一下命令,請參看個人前一篇文章MQTT快速入門

$ mosquitto_pub -d -p 1883 -h 10.69.94.176 -q 1 -t topic1 -m "Hello MQTT"
Client mosqpub|2052-SCNWCL0121 sending CONNECT
Client mosqpub|2052-SCNWCL0121 received CONNACK (0)
Client mosqpub|2052-SCNWCL0121 sending PUBLISH (d0, q1, r0, m1, 'topic1', ... (10 bytes))
Client mosqpub|2052-SCNWCL0121 received PUBACK (Mid: 1)
Client mosqpub|2052-SCNWCL0121 sending DISCONNECT
複製代碼

3. 進入Wireshark捕獲窗口,發現捕獲到了一些TCP和MQTT協議,以下:

packet-capture

4. 查看其中一條MQTT消息,好比Publish Message,點擊這一行。查看MQTT消息內容。其字節碼爲

32 14 00 06 74 6f 70 69 63 31 00 01 48 65 6c 6c 6f 20 4d 51 54 54
複製代碼

5. 來具體看一下消息內容

  • 首字節 0x32=0011 0010,對照首字節中的表,4位高字節爲0011=3,表示PUBLISH,4位低字節0010,分別表示DUP 0,QoS 1(佔兩位),Retain 0。

  • Remaining Length 0x14=20,表示剩餘消息長度爲20。

  • PUBLISH (QoS>0)報文消息包含可變頭部,其可變頭部包含topic name和Packet Identifier。其格式爲:

  • 00 06表示topic name的長度,因此topic name長度是6

  • 74 6f 70 69 63 31表示topic name的UTF8字符串,其值爲"topic1"。

  • 00 01是Packet Identifier,因此消息ID爲1。

  • 48 65 6c 6c 6f 20 4d 51 54 54是Payload,表示「Hello MQTT"的UTF8字節碼。


總結

咱們介紹了MQTT協議的消息格式,MQTT消息格式包含Fixed Header, Variable Header和Payload。由於MQTT消息格式很是精簡,因此能夠高效的傳輸數據。

Fixed Header中包含首字節,高4位用來表示報文類型,低4位用於類型控制。目前只有PUBLISH使用了類型控制字段。其它控制字段被保留而且必須與協議定義保持一致。

Fixed Header同時包含Remaining Length,這是剩餘消息長度,最大長度爲4字節,理論上一條MQTT最大能夠傳輸256MB數據。Remaining Length=Variable Header+Payload長度。

Variable Header是可變頭部,有些報文類型中須要包含可變頭部,可變頭部根據報文類型不一樣而不一樣。好比Packet Identifier在發佈,訂閱/取消訂閱等報文中都使用到。

Payload是消息內容,也只在某些報文類型中出現,其內容和格式也根據報文類型不一樣而不一樣。


全部文章在Github上同步,你也能夠訪問個人我的博客點擊查看

相關文章
相關標籤/搜索