雖然學習協議是枯燥的,可是熟悉協議自己倒是很重要的事情。若是能把其細節弄清楚,而且配合一些實驗來學習,就不會那麼枯燥了。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意思是剩餘長度,即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的意思是可變化的消息頭部。有些報文類型包含可變頭部,如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意思是消息載體的意思,如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協議,以下:
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是消息內容,也只在某些報文類型中出現,其內容和格式也根據報文類型不一樣而不一樣。