0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Linux應(yīng)用開發(fā)【第十六章】MQTT協(xié)議分析應(yīng)用開發(fā)

weidongshan ? 來源:weidongshan ? 作者:weidongshan ? 2021-12-10 19:32 ? 次閱讀

文章目錄

16 MQTT協(xié)議分析應(yīng)用開發(fā)

16.1 mqtt協(xié)議介紹

16.1.1 概述

16.1.2 特點

16.1.3 應(yīng)用

16.2 mqtt協(xié)議報文格式組成

16.2.1 mqtt控制報文結(jié)構(gòu)

16.2.2 mqtt固定報頭

16.2.3 mqtt控制報文類型

16.2.4 標(biāo)記

16.2.5 剩余長度

16.2.5.1 示例

16.2.6 可變報頭

16.2.7 有效載荷

16.3 報文分析

16.3.1 CONNECT-連接服務(wù)端

16.3.1.1 connect固定報頭

16.3.1.2 協(xié)議名字節(jié)組成

16.3.1.3 協(xié)議級別

16.3.1.4 連接標(biāo)記

16.3.1.5 保持連接

16.3.1.6 客戶端標(biāo)識符

16.3.1.7 遺囑主題

16.3.1.8 遺囑消息

16.3.1.9 用戶名和密碼

16.3.10.1 wirshark抓包分析connect報文

16.3.10.2 c語言構(gòu)造mqtt connect報文

16.3.2 CONNACK-確認(rèn)連接請求

16.3.2.1 固定報頭

16.3.2.2 可變報頭

16.3.2.3 CONNACK報文wireshark抓包分析

16.3.2.4 c語言構(gòu)造connect ack報文

16.3.3 PUBLISH-發(fā)布消息

16.3.3.1 固定報頭

16.3.3.2 抓包分析PUBLISH報文

16.3.3.3 構(gòu)造publish 報文

16.3.4 PUBREC-發(fā)布收到

16.3.4.1 固定報頭

16.3.4.2 PUBREC抓包報文

16.3.4.3 c語言構(gòu)造pubrec報文

16.3.5 PUBREL-發(fā)布釋放

16.3.5.1 固定報頭

16.3.5.2 PUBREL抓包報文

16.3.5.3 c語言構(gòu)造pubrel報文

16.3.6 PUBCOMP-發(fā)布完成

16.3.6.1 固定報頭

16.3.6.2 PUBCOMP抓包報文

16.3.6.3 c語言構(gòu)造pubcom報文

16.3.7 PINGREQ-心跳請求

16.3.7.1 固定報頭

16.3.7.2 PINGREQ 抓包報文

16.3.7.3 c語言構(gòu)造pingreq報文

16.3.8 PINGRESP – 心跳響應(yīng)

16.3.8.1 固定報頭

16.3.8.2 PINGRESP 抓包報文

16.3.8.3 c語言構(gòu)造pinpresp報文

16.3.9 DISCONNECT –斷開連接

16.3.9.1 固定報頭

16.3.9.2 DISCONNECT 抓包報文

16.3.9.3 c語言構(gòu)造disconnect報文

16.3.10 SUBSCRIBE-訂閱主題

16.3.10.1 固定報頭

16.3.10.2 SUBSCRIBE報文抓包

16.3.10.3 c語言構(gòu)造subscribe報文

16.3.11 SUBACK –訂閱確認(rèn)

16.3.11.1 固定報頭

3.11.2 SUBACK抓包報文

16.3.11.3 c語言構(gòu)造suback報文

16.3.12 UNSUBSCRIBE –取消訂閱

16.3.12.1 固定報頭

16.3.12.2 UNSUBSCRIBE抓包報文

16.3.12.3 c語言構(gòu)造unsubscribe報文

16.3.13 UNSUBACK –取消訂閱確認(rèn)

16.3.13.1 固定報頭

16.3.12.2 UNSUBSCRIBE ACK抓包報文

16.3.12.3 c語言構(gòu)造unsubscribe報文

16.3.14 服務(wù)端與客戶端交互操作過程

16.3.14.1 編譯

16.3.14.2 執(zhí)行

16 MQTT協(xié)議分析應(yīng)用開發(fā)

16.1 mqtt協(xié)議介紹

16.1.1 概述

MQTT是一個客戶端服務(wù)端架構(gòu)的發(fā)布/訂閱模式的消息傳輸協(xié)議。它的設(shè)計思想是輕巧、開放、簡單、規(guī)范,易于實現(xiàn)。這些特點使得它對很多場景來說都是很好的選擇,特別是對于受限的環(huán)境如機器與機器的通信M2M)以及物聯(lián)網(wǎng)環(huán)境(IoT)。

16.1.2 特點

a) 開放消息協(xié)議,簡單易實現(xiàn)

b) 發(fā)布訂閱模式,一對多消息發(fā)布

c) 基于TCP/IP網(wǎng)絡(luò)連接

d) 1字節(jié)固定報頭,2字節(jié)心跳報文,報文結(jié)構(gòu)緊湊

e) 消息QoS支持,可靠傳輸保證

16.1.3 應(yīng)用

MQTT協(xié)議廣泛應(yīng)用于物聯(lián)網(wǎng)、移動互聯(lián)網(wǎng)、智能硬件、車聯(lián)網(wǎng)、電力能源等領(lǐng)域。

a) 物聯(lián)網(wǎng)M2M通信,物聯(lián)網(wǎng)大數(shù)據(jù)采集

b) Android消息推送,WEB消息推送

c) 移動即時消息,例如Facebook Messenger

d) 智能硬件、智能家具、智能電器

e) 車聯(lián)網(wǎng)通信,電動車站樁采集

f) 智慧城市、遠程醫(yī)療、遠程教育

g) 電力、石油與能源等行業(yè)市場

16.2 mqtt協(xié)議報文格式組成

16.2.1 mqtt控制報文結(jié)構(gòu)

MQTT 協(xié)議通過交換預(yù)定義的 MQTT 控制報文來通信。 這一節(jié)描述這些報文的格式。MQTT 控制報文由三部分組成,如下圖:

pYYBAGGzOreAP0HeAABZHrWeVHA190.png

圖2.1 mqtt報文組成

16.2.2 mqtt固定報頭

每個 MQTT 控制報文都包含一個固定報頭, 固定報頭的格式如下圖:

poYBAGGzOreALMsLAABLE738lek049.png

圖2.2 mqtt固定報頭

16.2.3 mqtt控制報文類型

位置: 第 1 個字節(jié), 二進制位 7-4,表示為 4 位無符號值。

MQTT 控制報文的類型:如下表:

Reserved 0 禁止 保留
CONNECT 1 客戶端到服務(wù)端 客戶端請求連接服務(wù)端
CONNACK 2 服務(wù)端到客戶端 連接報文確認(rèn)
PUBLISH 3 兩個方向都允許 發(fā)布消息
PUBACK 4 兩個方向都允許 QoS 1消息發(fā)布收到確認(rèn)
PUBREC 5 兩個方向都允許 發(fā)布收到(保證交付第一步)
PUBREL 6 兩個方向都允許 發(fā)布釋放(保證交付第二步)
PUBCOMP 7 兩個方向都允許 QoS 2消息發(fā)布完成(保證交互第三步)
SUBSCRIBE 8 客戶端到服務(wù)端 客戶端訂閱請求
SUBACK 9 服務(wù)端到客戶端 訂閱請求報文確認(rèn)
UNSUBSCRIBE 10 客戶端到服務(wù)端 客戶端取消訂閱請求
UNSUBACK 11 服務(wù)端到客戶端 取消訂閱報文確認(rèn)
PINGREQ 12 客戶端到服務(wù)端 心跳請求
PINGRESP 13 服務(wù)端到客戶端 心跳響應(yīng)
DISCONNECT 14 客戶端到服務(wù)端 客戶端斷開連接
Reserved 15 禁止 保留
名字 報文流動方向 描述

16.2.4 標(biāo)記

固定報頭第 1 個字節(jié)的剩余的 4 位 [3-0]包含每個 MQTT 控制報文類型特定的標(biāo)志 。標(biāo)記位說明如下表所示:

CONNECT Reserved 0 0 0 0
CONNACK Reserved 0 0 0 0
PUBLISH Used in MQTT 3.1.1 DUP1 QoS2 QoS2 RETAIN3
PUBACK Reserved 0 0 0 0
PUBREC Reserved 0 0 0 0
PUBREL Reserved 0 0 1 0
PUBCOMP Reserved 0 0 0 0
SUBSCRIBE Reserved 0 0 1 0
SUBACK Reserved 0 0 0 0
UNSUBSCRIBE Reserved 0 0 1 0
UNSUBACK Reserved 0 0 0 0
PINGREQ Reserved 0 0 0 0
PINGRESP Reserved 0 0 0 0
DISCONNECT Reserved 0 0 0 0
控制報文 固定報頭標(biāo)志 Bit 3 Bit 2 Bit 1 Bit 0

DUP1 =控制報文的重復(fù)分發(fā)標(biāo)志
QoS2 = PUBLISH 報文的服務(wù)質(zhì)量等級
RETAIN3 = PUBLISH 報文的保留標(biāo)志

16.2.5 剩余長度

位置:從第二個字節(jié)開始。剩余長度( Remaining Length) 表示當(dāng)前報文剩余部分的字節(jié)數(shù), 包括可變報頭和負(fù)載的數(shù)據(jù)。 剩余長度不包括用于編碼剩余長度字段本身的字節(jié)數(shù)。

pYYBAGGzOreALHPfAABbPCs-lGo988.png

圖2.3 剩余長度包含的報文范圍

剩余長度字段使用一個變長度編碼方案, 對小于 128 的值它使用單字節(jié)編碼。 更大的值按下面的方式處理。低 7 位有效位用于編碼數(shù)據(jù),最高有效位用于指示是否有更多的字節(jié)。 因此每個字節(jié)可以編碼 128 個數(shù)值和一個延續(xù)位( continuation bit) 。 剩余長度字段最大 4 個字節(jié)。

例如, 十進制數(shù) 64 會被編碼為一個字節(jié), 數(shù)值是 64, 十六進制表示為 0x40,。十進制數(shù)字321(=65+2*128)被編碼為兩個字節(jié), 最低有效位在前。 第一個字節(jié)是 65+128=193。 注意最高位為
1 表示后面至少還有一個字節(jié)。 第二個字節(jié)是 2。

16.2.5.1 示例

123456 = 964 x 128 + 64 964 = 7x128 + 68 7 < 128 也就是123456 = (7 x 128 + 68)x128 + 64 展開:64 + 68 x128 + 7x128x128 第一字節(jié):64 | 0x80 = x (0x80=0x1000 0000或上最高位表示是否還有更多的字節(jié)) 第二字節(jié):68 | 0x80 = y (0x80=0x1000 0000或上最高位表示是否還有更多的字節(jié)) 第三字節(jié):7=z c語言表示:unsigned char len_byte[4] = { 64 | 128 , 68 | 128, 7 , 0 } 反過來,如果要算出123456 x-128 + (y-128)*128 + z x 128 x 128

把剩余長度轉(zhuǎn)換成字節(jié)表示:

poYBAGGzOriAT-5FAAB8t0UjIO8044.png

把字節(jié)轉(zhuǎn)換成剩余長度表示:

pYYBAGGzOriAXXQLAADfFqQ5dco937.png

16.2.6 可變報頭

某些 MQTT 控制報文包含一個可變報頭部分。 它在固定報頭和負(fù)載之間??勺儓箢^的內(nèi)容根據(jù)報文類型的不同而不同。報文標(biāo)識符是可變報頭一種,可變報頭的報文標(biāo)識符( Packet Identifier) 字段存在于在多個類型的報文里。

報文標(biāo)識符類型如下圖:

poYBAGGzOriAUoMcAAAvI1lyyC8134.png

圖2.4 報文標(biāo)識符

很多控制報文的可變報頭部分包含一個兩字節(jié)的報文標(biāo)識符字段。 這些報文是 PUBLISH( QoS>0 時) ,PUBACK, PUBREC, PUBREL, PUBCOMP, SUBSCRIBE, SUBACK, UNSUBSCIBE,UNSUBACK,如下表所示:

CONNECT 不需要
CONNACK 不需要
PUBLISH 需要(如果QoS > 0)
PUBACK 需要
PUBREC 需要
PUBREL 需要
PUBCOMP 需要
SUBSCRIBE 需要
SUBACK 需要
UNSUBSCRIBE 需要
UNSUBACK 需要
PINGREQ 不需要
PINGRESP 不需要
DISCONNECT 不需要
控制報文 報文標(biāo)識符字段

客戶端和服務(wù)端彼此獨立地分配報文標(biāo)識符。 因此,客戶端服務(wù)端組合使用相同的報文標(biāo)識符可以實現(xiàn)并發(fā)的消息交換。

例如,當(dāng)client發(fā)送一個packet Identifier =0x1234的報文給server時,server的回復(fù)報文packet identifier 必須是0x1234,Packet identifier 從1開始遞增,到達65535時,又從1開始計算。

poYBAGGzOrmATGuUAAAbUxUPFwg557.png

圖2.5 需要 Packet Identifier 的報文類型交互示意圖

16.2.7 有效載荷

某些 MQTT 控制報文在報文的最后部分包含一個有效載荷,帶有有效載荷報文類型如下表所示:

CONNECT 需要
CONNACK 不需要
PUBLISH 可選
PUBACK 不需要
PUBREC 不需要
PUBREL 不需要
PUBCOMP 不需要
SUBSCRIBE 需要
SUBACK 需要
UNSUBSCRIBE 需要
UNSUBACK 不需要
PINGREQ 不需要
PINGRESP 不需要
DISCONNECT 不需要
控制報文 有效載荷

16.3 報文分析

16.3.1 CONNECT-連接服務(wù)端

客戶端到服務(wù)端的網(wǎng)絡(luò)連接建立(完成三次握手)后,客戶端發(fā)送給服務(wù)端的第一個報文必須是 CONNECT 報文。

pYYBAGGzOrqAOrw9AABm3d75Hww292.png

圖3.1 三次握手與mqtt connect交互過程

在一個網(wǎng)絡(luò)連接上,客戶端只能發(fā)送一次 CONNECT 報文。服務(wù)端必須將客戶端發(fā)送的第二個 CONNECT報文當(dāng)作協(xié)議違規(guī)處理并斷開客戶端的連接。

有效載荷包含一個或多個編碼的字段。 包括客戶端的唯一標(biāo)識符, Will 主題, Will 消息, 用戶名和密碼。 除了客戶端標(biāo)識之外, 其它的字段都是可選的, 基于標(biāo)志位來決定可變報頭中是否需要包含這些字段。

poYBAGGzOrqAdWzrAAD4p-euB-8009.png

圖3.2 connect報文組成

16.3.1.1 connect固定報頭

bit 7 6 5 4 3 2 1 0
Byte1 Mqtt報文類型(1) Reserved(保留位)
0 0 0 1 0 0 0 0
Byte2~n 剩余長度

表格3.1

16.3.1.2 協(xié)議名字節(jié)組成

說明 7 6 5 4 3 2 1 0
協(xié)議名
Byte1 協(xié)議名長度MSB(0) 0 0 0 0 0 0 0 0
Byte2 協(xié)議名長度LSB(4) 0 0 0 0 0 1 0 0
Byte3 ‘M’ 0 1 0 0 1 1 0 1
Byte4 ‘Q’ 0 1 0 1 0 0 0 1
Byte5 ‘T’ 0 1 0 1 0 1 0 0
Byte6 ‘T’ 0 1 0 1 0 1 0 0

數(shù)據(jù)包檢測工具, 例如防火墻, 可以使用協(xié)議名來識別 MQTT 流量。

16.3.1.3 協(xié)議級別

說明 7 6 5 4 3 2 1 0
協(xié)議級別
Byte7 Level(4) 0 0 0 0 0 1 0 0

客戶端用 8 位的無符號值表示協(xié)議的修訂版本。對于 3.1.1 版協(xié)議,協(xié)議級別字段的值是 4(0x04)。如果發(fā)現(xiàn)不支持的協(xié)議級別,服務(wù)端必須給發(fā)送一個返回碼為 0x01(不支持的協(xié)議級別)的CONNACK 報文響應(yīng)CONNECT 報文, 然后斷開客戶端的連接。

16.3.1.4 連接標(biāo)記

用戶名標(biāo)記 用戶密碼標(biāo)記 Will retain Will qos Will flag 清除會話 reserved
Byte 8 x x x x x x 0
bit 7 6 5 4 3 2 1 0

bit1清除會話

一般來說, 客戶端連接時總是將清理會話標(biāo)志設(shè)置為 0 或 1, 并且不交替使用兩種值。 這個選擇取決于具體的應(yīng)用。 清理會話標(biāo)志設(shè)置為 1 的客戶端不會收到舊的應(yīng)用消息, 而且在每次連接成功后都需要重新訂閱任何相關(guān)的主題。清理會話標(biāo)志設(shè)置為 0 的客戶端會收到所有在它連接斷開期間發(fā)布的 QoS 1 和 QoS 2 級別的消息。因此, 要確保不丟失連接斷開期間的消息, 需要使用 QoS 1 或QoS 2 級別,同時將清理會話標(biāo)志設(shè)置為 0。

Bit2遺囑標(biāo)志

遺囑標(biāo)志(Will Flag) 被設(shè)置為 1,表示如果連接請求被接受了, 遺囑(Will Message) 消息必須被存儲在服務(wù)端并且與這個網(wǎng)絡(luò)連接關(guān)聯(lián)。之后網(wǎng)絡(luò)連接關(guān)閉時,服務(wù)端必須發(fā)布這個遺囑消息, 除非服務(wù)端收到DISCONNECT 報文時刪除了這個遺囑消息。

Bit3和 bit4遺囑 QoS

這兩位用于指定發(fā)布遺囑消息時使用的服務(wù)質(zhì)量等級, 如果遺囑標(biāo)志被設(shè)置為 0, 遺囑 QoS 也必須設(shè)置為 0(0x00),如果遺囑標(biāo)志被設(shè)置為 1, 遺囑 QoS 的值可以等于 0(0x00), 1(0x01), 2(0x02), 它的值不能等于 3。

Bit5遺囑保留

如果遺囑消息被發(fā)布時需要保留,需要指定這一位的值, 如果遺囑標(biāo)志被設(shè)置為 0, 遺囑保留(Will Retain) 標(biāo)志也必須設(shè)置為 0 。
如果遺囑標(biāo)志被設(shè)置為 1:
· 如果遺囑保留被設(shè)置為 0, 服務(wù)端必須將遺囑消息當(dāng)作非保留消息發(fā)布 。
· 如果遺囑保留被設(shè)置為 1, 服務(wù)端必須將遺囑消息當(dāng)作保留消息發(fā)布。

Bit7用戶名標(biāo)志

如果用戶名(User Name) 標(biāo)志被設(shè)置為 0, 有效載荷中不能包含用戶名字段。

如果用戶名(User Name) 標(biāo)志被設(shè)置為 1, 有效載荷中必須包含用戶名字段。

Bit6用戶名密碼標(biāo)記

如果密碼(Password) 標(biāo)志被設(shè)置為 0, 有效載荷中不能包含密碼字段 。
如果密碼(Password) 標(biāo)志被設(shè)置為 1, 有效載荷中必須包含密碼字段 。
如果用戶名標(biāo)志被設(shè)置為 0, 密碼標(biāo)志也必須設(shè)置為 0 。

16.3.1.5 保持連接

bit 7 6 5 4 3 2 1 0
Byte9 保持連接 Keep Alive MSB
Byte10 保持連接 Keep Alive LSB

a) 保持連接(Keep Alive) 是一個以秒為單位的時間間隔,表示為一個 16 位的字,它是指在客戶端傳輸完成。

b) 一個控制報文的時刻到發(fā)送下一個報文的時刻, 兩者之間允許空閑的最大時間間隔。 客戶端負(fù)責(zé)保證控制。

c) 報文發(fā)送的時間間隔不超過保持連接的值。 如果沒有任何其它的控制報文可以發(fā)送, 客戶端必須發(fā)送一個PINGREQ 報文。

d) 不管保持連接的值是多少,客戶端任何時候都可以發(fā)送 PINGREQ 報文,并且使用 PINGRESP 報文判斷網(wǎng)絡(luò)和服務(wù)端的活動狀態(tài)。

e) 如果保持連接的值非零,并且服務(wù)端在一點五倍的保持連接時間內(nèi)沒有收到客戶端的控制報文, 它必須斷開客戶端的網(wǎng)絡(luò)連接, 認(rèn)為網(wǎng)絡(luò)連接已斷開。

f) 客戶端發(fā)送了 PINGREQ 報文之后, 如果在合理的時間內(nèi)仍沒有收到 PINGRESP 報文, 它應(yīng)該關(guān)閉到服務(wù)端的網(wǎng)絡(luò)連接。

g) 保持連接的值為零表示關(guān)閉保持連接功能。 這意味著,服務(wù)端不需要因為客戶端不活躍而斷開連接。 注意:不管保持連接的值是多少, 任何時候,只要服務(wù)端認(rèn)為客戶端是不活躍或無響應(yīng)的, 可以斷開客戶端的連接。

16.3.1.6 客戶端標(biāo)識符

服務(wù)端使用客戶端標(biāo)識符 (ClientId) 識別客戶端。 連接服務(wù)端的每個客戶端都有唯一的客戶端標(biāo)識符(ClientId) ??蛻舳撕头?wù)端都必須使用 ClientId 識別兩者之間的 MQTT 會話相關(guān)的狀態(tài), 客戶端標(biāo)識符 (ClientId) 必須存在而且必須是 CONNECT 報文有效載荷的第一個字段,客戶端標(biāo)識符必須是UTF-8 編碼字符串。

16.3.1.7 遺囑主題

如果遺囑標(biāo)志被設(shè)置為 1, 有效載荷的下一個字段是遺囑主題(Will Topic) 。 遺囑主題必須是 UTF-8 編碼字符串。

16.3.1.8 遺囑消息

如果遺囑標(biāo)志被設(shè)置為 1, 有效載荷的下一個字段是遺囑消息。 遺囑消息定義了將被發(fā)布到遺囑主題的應(yīng)用消息。

16.3.1.9 用戶名和密碼

如果用戶名( User Name) 標(biāo)志被設(shè)置為 1, 有效載荷的下一個字段就是它。 用戶名必須是定義的UTF-8 編碼字符串。服務(wù)端可以將它用于身份驗證和授權(quán)。

如果密碼( Password) 標(biāo)志被設(shè)置為 1, 有效載荷的下一個字段就是它。密碼字段包含一個兩字節(jié)的長度字段, 長度表示二進制數(shù)據(jù)的字節(jié)數(shù)( 不包含長度字段本身占用的兩個字節(jié)),后面跟著 0 到 65535 字節(jié)的二進制數(shù)據(jù)。

pYYBAGGzOrqAEGAWAAA_goSeZQQ703.png

圖3.2 用戶名和密碼在connect報文中的組成

16.3.10.1 wirshark抓包分析connect報文

從抓包可知,從上到下分別是固定報頭,可變報頭,連接標(biāo)記,保持連接,用戶名,用名密碼,其中沒有遺囑相關(guān)消息字段,與3.1.1節(jié)分析的固定報頭組成分析一致。

poYBAGGzOruALhghAAFZfL_zoXE234.png

圖 3.3使用wireshark抓包分析connect報文組成格式

16.3.10.2 c語言構(gòu)造mqtt connect報文

static uint8_t client_id[512] = {"mqtt_client"}; static uint8_t user_name[512] = {"mqtt"}; static uint8_t passwd[512] = {"12345678"}; #define KEEP_ALIVE 20 int mqtt_connect(int sockfd) { uint8 flags = 0x00; uint8 *packet = NULL; uint16 packet_length = 0; uint16 clientidlen = strlen(client_id); uint16 usernamelen = strlen(user_name); uint16 passwordlen = strlen(passwd); uint16 payload_len = clientidlen + 2; // Variable header uint8 var_header[10] = { 0x00,0x04,/*len*/ 0x4d,0x51,0x54,0x54,/*mqtt*/ 0x04,/*協(xié)議版本*/}; uint8 fixedHeaderSize = 2; // Default size = one byte Message Type + one byte Remaining Length uint8 remainLen = 0; uint8 *fixed_header = NULL; uint16 offset = 0; // Preparing the flags if(usernamelen) { /*用戶名長度(可選)*/ payload_len += usernamelen + 2; flags |= MQTT_USERNAME_FLAG;/*或上用戶名標(biāo)記*/ } if(passwordlen) { /*用戶密碼(可選)*/ payload_len += passwordlen + 2; flags |= MQTT_PASSWORD_FLAG;/*用戶密碼標(biāo)記位*/ } flags |= MQTT_CLEAN_SESSION; var_header[7] = flags;/*連接標(biāo)記*/ var_header[8] = KEEP_ALIVE>>8;/*保持連接字段,占用兩個字節(jié)*/ var_header[9] = KEEP_ALIVE&0xFF; remainLen = sizeof(var_header)+payload_len; /*剩余長度,也就是可變報頭加上負(fù)載的長度*/ if (remainLen > 127) { fixedHeaderSize++;// add an additional byte for Remaining Length } fixed_header = (uint8 *)malloc(fixedHeaderSize); /*固定報頭*/ // Message Type *fixed_header = MQTT_MSG_CONNECT;/*報文類型,connect*/ if (remainLen <= 127) {// Remaining Length,剩余長度計算,可變長編碼 *(fixed_header+1) = remainLen; } else { // first byte is remainder (mod) of 128, then set the MSB to indicate more bytes *(fixed_header+1) = remainLen % 128; *(fixed_header+1) = *(fixed_header+1) | 0x80; // second byte is number of 128s *(fixed_header+2) = remainLen / 128; } packet_length = fixedHeaderSize+sizeof(var_header)+payload_len;/*固定報頭+可變報頭+負(fù)載長度*/ packet = (uint8 *)malloc(packet_length);/*分配內(nèi)存*/ memset(packet, 0, packet_length); memcpy(packet, fixed_header, fixedHeaderSize);/*填充固定報頭*/ free(fixed_header); offset += fixedHeaderSize; memcpy(packet+offset, var_header, sizeof(var_header));/*填充可變報頭*/ offset += sizeof(var_header); packet[offset++] = clientidlen>>8;// Client ID - UTF encoded,填充clientid長度+clientid packet[offset++] = clientidlen&0xFF; memcpy(packet+offset, client_id, clientidlen); offset += clientidlen; if(usernamelen) {// Username - UTF encoded,填充用戶名+用戶名長度 packet[offset++] = usernamelen>>8; packet[offset++] = usernamelen&0xFF; memcpy(packet+offset, user_name, usernamelen); offset += usernamelen; } if(passwordlen) {// Password - UTF encoded,填充用戶密碼+用戶名密碼長度 packet[offset++] = passwordlen>>8; packet[offset++] = passwordlen&0xFF; memcpy(packet+offset, passwd, passwordlen); offset += passwordlen; } // Send the packet if (client_send(sockfd,packet, packet_length) < 0){ free(packet); return -1; } free(packet); return 1; }

16.3.2 CONNACK-確認(rèn)連接請求

服務(wù)端發(fā)送 CONNACK 報文響應(yīng)從客戶端收到的 CONNECT 報文。服務(wù)端發(fā)送給客戶端的第一個報文必須是 CONNACK。

16.3.2.1 固定報頭

bit 7 6 5 4 3 2 1 0
Byte1 MQTT 控制報文類型 (2) Reserved 保留位
0 0 1 0 0 0 0 0
Byte2 剩余長度
0 0 0 0 0 0 1 0

剩余長度字段表示可變報頭的長度。 對于 CONNACK 報文這個值等于 2。

16.3.2.2 可變報頭

描述 7 6 5 4 3 2 1 0
連接確認(rèn)標(biāo)記 保留位 SP1
Byte1 0 0 0 0 0 0 0 X
連接返回碼
Byte2 x x x x x x x x

Byte1,Bit0連接確認(rèn)標(biāo)志

位 7-1 是保留位且必須設(shè)置為 0,

對于bit0,如果服務(wù)端收到一個 CleanSession 為 0 的連接, 當(dāng)前會話標(biāo)志的值取決于服務(wù)端是否已經(jīng)保存了 ClientId對應(yīng)客戶端的會話狀態(tài)。 如果服務(wù)端已經(jīng)保存了會話狀態(tài), 它必須將 CONNACK 報文中的當(dāng)前會話標(biāo)志設(shè)置為 1 。 如果服務(wù)端沒有已保存的會話狀態(tài), 它必須將 CONNACK 報文中的當(dāng)前會話設(shè)置為 0。 還需要將 CONNACK 報文中的返回碼設(shè)置為 0。

連接返回碼

如果服務(wù)端發(fā)送了一個包含非零返回碼的 CONNACK 報文, 那么它必須關(guān)
閉網(wǎng)絡(luò)連接。

0 0x00 連接已被服務(wù)端接受
1 0x01 服務(wù)端不支持客戶端請求的協(xié)議版本
2 0x02 客戶端標(biāo)識符是正確的 UTF-8 編碼, 但服務(wù) 端不允許使用
3 0x03 網(wǎng)絡(luò)連接已建立, 但 MQTT 服務(wù)不可用
4 0x04 用戶名或密碼的數(shù)據(jù)格式無效
5 0x05 客戶端未被授權(quán)連接到此服務(wù)器
6-255 保留
返回碼響應(yīng) 描述

CONNACK沒有有效載荷。

16.3.2.3 CONNACK報文wireshark抓包分析

pYYBAGGzOsGAHt38AABY_FbZl6c369.png

圖3.4 CONNACK 抓包報文

16.3.2.4 c語言構(gòu)造connect ack報文

void mqtt_connect_ack(int sockfd) { uint8_t cmd[]={ 0x20/*報文類型*/, 0x02/*剩余長度*/ ,0x00,0x00/*最后兩個字節(jié)可變報頭表示返回狀態(tài)碼*/ }; send_msg(sockfd,cmd,sizeof(cmd)); socket_record_t *socket_record = look_up_by_sokfd(sockfd); if(socket_record==NULL){ return; } socket_record->is_connect=0x01; }

16.3.3 PUBLISH-發(fā)布消息

PUBLISH 控制報文是指從客戶端向服務(wù)端或者服務(wù)端向客戶端傳輸一個應(yīng)用消息。

poYBAGGzOseAVCQtAABkswXSRKs395.png

圖 3.5 publish報文組成格式

16.3.3.1 固定報頭

bit 7 6 5 4 3 2 1 0
Byte 1 MQTT報文類型(3) dup Qos等級 RETAIN
0 0 1 1 x x x x
Byte2 剩余長度

Bit3 dup

如果 DUP 標(biāo)志被設(shè)置為 0, 表示這是客戶端或服務(wù)端第一次請求發(fā)送這個 PUBLISH 報文。 如果 DUP 標(biāo)志被設(shè)置為 1,表示這可能是一個早前報文請求的重發(fā)??蛻舳嘶蚍?wù)端請求重發(fā)一個 PUBLISH 報文時, 必須將 DUP 標(biāo)志設(shè)置為 1.。 對于 QoS0 的消息, DUP 標(biāo)志必須設(shè)置為 0。

Bit1和bit2 qos等級

0 0 0 最多分發(fā)一次
1 0 1 至少分發(fā)一次
2 1 0 只分發(fā)一次
- 1 1 保留不使用
Qos值 bit2 bit1 描述

qos由發(fā)送端決定,發(fā)送端發(fā)送什么qos的消息,接收端就回復(fù)什么qos的消息。

pYYBAGGzOs2AFQm9AACpcGqPY4s186.png

不同qos等級mqtt報文交互流程

Bit0保留標(biāo)記位

一般設(shè)置為0。

剩余長度

等于可變報頭的長度加上有效載荷的長度。

可變報頭

可變報頭按順序包含主題名和標(biāo)識符。主題,用于識別有效載荷數(shù)據(jù)應(yīng)該被發(fā)布到哪一個信息通道,標(biāo)識符,只有當(dāng) QoS 等級是 1 或 2 時,報文標(biāo)識符( Packet Identifier) 字段才能出現(xiàn)在 PUBLISH 報文中。

16.3.3.2 抓包分析PUBLISH報文

poYBAGGzOtOAMC2WAACMX12OT68100.png

圖 3.6 PUBLISH 抓包報文

16.3.3.3 構(gòu)造publish 報文

int mqtt_publish_with_qos(int sockfd,const char* topic, const char* msg, uint16 msgl, uint8 retain, uint8 qos, uint16* message_id) { socket_record_t *socket_record = look_up_by_sokfd(sockfd); if(NULL == socket_record){ return -1; } DEBUG_INFO("sockfd:%d",socket_record->sockfd); uint16 topiclen = strlen(topic); uint16 msglen = msgl; uint8 *var_header = NULL; // Topic size (2 bytes), utf-encoded topic uint8 *fixed_header = NULL; uint8 fixedHeaderSize = 0,var_headerSize = 0; // Default size = one byte Message Type + one byte Remaining Length uint16 remainLen = 0; uint8 *packet = NULL; uint16 packet_length = 0; uint8 qos_flag = MQTT_QOS0_FLAG; /*qos標(biāo)記*/ uint8 qos_size = 0; // No QoS included if(qos == 1) { qos_size = 2; // 2 bytes for QoS qos_flag = MQTT_QOS1_FLAG; } else if(qos == 2) { qos_size = 2; // 2 bytes for QoS qos_flag = MQTT_QOS2_FLAG; } // Variable header var_headerSize = topiclen/*主題內(nèi)容*/+2/*主題長度占用兩字節(jié)*/+qos_size/*標(biāo)識符*/; var_header = (uint8 *)malloc(var_headerSize); memset(var_header, 0, var_headerSize); *var_header = topiclen>>8; *(var_header+1) = topiclen&0xFF; memcpy(var_header+2, topic, topiclen); if(qos_size) {//qos1和qos2的報文需要填充標(biāo)識符,有點像tcp的seq socket_record->publish_seq++; if(socket_record->publish_seq == 0){ //unsigned short 表示范圍0~65535,標(biāo)識符必須是非零整數(shù) socket_record->publish_seq = 1; } var_header[topiclen+2] = (socket_record->publish_seq & 0xff00)>>8; var_header[topiclen+3] = socket_record->publish_seq & 0x00ff; if(message_id) { *message_id = socket_record->publish_seq; } } fixedHeaderSize = 2; // Default size = one byte Message Type + one byte Remaining Length remainLen = var_headerSize+msglen; if (remainLen > 127) {/*剩余長度*/ fixedHeaderSize++; // add an additional byte for Remaining Length } fixed_header = (uint8 *)malloc(fixedHeaderSize);/*固定報頭+剩余長度*/ // Message Type, DUP flag, QoS level, Retain *fixed_header = MQTT_MSG_PUBLISH | qos_flag;/*報文類型和qos標(biāo)記*/ if(retain) { *fixed_header |= MQTT_RETAIN_FLAG;/*是否保留*/ } // Remaining Length,剩余長度 if (remainLen <= 127) { *(fixed_header+1) = remainLen; } else { // first byte is remainder (mod) of 128, then set the MSB to indicate more bytes *(fixed_header+1) = remainLen % 128; *(fixed_header+1) = *(fixed_header+1) | 0x80; // second byte is number of 128s *(fixed_header+2) = remainLen / 128; } packet_length = fixedHeaderSize+var_headerSize+msglen;/*固定報頭+可變報頭+負(fù)載長度*/ packet = (uint8 *)malloc(packet_length); memset(packet, 0, packet_length); memcpy(packet, fixed_header, fixedHeaderSize);/*填充固定報頭*/ memcpy(packet+fixedHeaderSize, var_header, var_headerSize);/*填充可變報頭*/ memcpy(packet+fixedHeaderSize+var_headerSize, msg, msglen);/*負(fù)載*/ free(var_header); free(fixed_header); send_msg(sockfd,packet , packet_length); free(packet); return 1; }

16.3.4 PUBREC-發(fā)布收到

PUBREC 報文是對 QoS 等級 2 的 PUBLISH 報文的響應(yīng)。它是 QoS 2 等級協(xié)議交換的第二個報文。

16.3.4.1 固定報頭

bit 7 6 5 4 3 2 1 0
Byte 1 MQTT報文類型(5) 保留位
0 1 0 1 0 0 0 0
Byte2 剩余長度

剩余長度

表示可變報頭的長度。 對 PUBREC 報文它的值等于 2。

可變報頭

bit 7 6 5 4 3 2 1 0
Byte1 報文標(biāo)識符MSB
Byte2 報文標(biāo)識符LSB

有效載荷

PUBREC 報文沒有有效載荷。

16.3.4.2 PUBREC抓包報文

pYYBAGGzOtiACPMOAAA3fmTL1oQ105.png

圖 3.7 PUBREC抓包報文圖示

16.3.4.3 c語言構(gòu)造pubrec報文

//如果是PUBREC報文,head_type=0x50 void mqtt_qos2_pubrec(int sockfd , unsigned char *data,unsigned char head_type) { uint16 msg_id = mqtt_parse_msg_id(data);/*報文標(biāo)識符,回復(fù)報文和接受報文的標(biāo)識符必須一樣*/ unsigned char qos2_pubrec_respon[]={head_type/*固定報頭*/,0x02/*剩余長度*/, (msg_id&0xff00)>>8 , msg_id&0x00ff/*最后兩個字節(jié)是報文標(biāo)識符*/}; send_msg(sockfd,qos2_pubrec_respon,sizeof(qos2_pubrec_respon)); }

16.3.5 PUBREL-發(fā)布釋放

PUBREL 報文是對 PUBREC 報文的響應(yīng)。 它是 QoS 2 等級協(xié)議交換的第三個報文。

16.3.5.1 固定報頭

bit 7 6 5 4 3 2 1 0
Byte 1 MQTT報文類型(6) 保留位
0 1 1 0 0 0 0 0
Byte2 剩余長度

剩余長度

表示可變報頭的長度。 對 PUBREL 報文它的值等于 2。

可變報頭

bit 7 6 5 4 3 2 1 0
Byte1 報文標(biāo)識符MSB
Byte2 報文標(biāo)識符LSB

有效載荷

PUBREL 報文沒有有效載荷。

16.3.5.2 PUBREL抓包報文

poYBAGGzOt6ASFeNAAA09LDGivU386.png

圖 3.8 PUBREL抓包報文圖示

16.3.5.3 c語言構(gòu)造pubrel報文

//head_type=0x62 void mqtt_qos2_pubrel(int sockfd , unsigned char *data,unsigned char head_type) { uint16 msg_id = mqtt_parse_msg_id(data); unsigned char qos2_pubrel_respon[]={head_type/*報文類型*/,0x02/*剩余長度*/, (msg_id & 0xff00)>>8 , msg_id & 0x00ff/*最后兩個字節(jié)是報文標(biāo)識符*/}; send_msg(sockfd,qos2_pubrel_respon,sizeof(qos2_pubrel_respon)); }

16.3.6 PUBCOMP-發(fā)布完成

PUBCOMP 報文是對 PUBREL 報文的響應(yīng)。 它是 QoS 2 等級協(xié)議交換的第四個也是最后一個報文。

16.3.6.1 固定報頭

bit 7 6 5 4 3 2 1 0
Byte 1 MQTT報文類型(7) 保留位
0 1 1 1 0 0 0 0
Byte2 剩余長度

剩余長度

表示可變報頭的長度。 對 PUBCOMP 報文它的值等于 2。

可變報頭

bit 7 6 5 4 3 2 1 0
Byte1 報文標(biāo)識符MSB
Byte2 報文標(biāo)識符LSB

有效載荷

PUBCOMP 報文沒有有效載荷。

16.3.6.2 PUBCOMP抓包報文

pYYBAGGzOuSACFmjAAA6NB9m4qk558.png

圖 3.9 PUBCOMP抓包報文圖示

16.3.6.3 c語言構(gòu)造pubcom報文

//head_type=0x70 void mqtt_qos2_pubcomp(int sockfd , unsigned char *data,unsigned char head_type) { uint16 msg_id = mqtt_parse_msg_id(data);/*報文標(biāo)識符*/ unsigned char qos2_pubcomp_respon[]={head_type/*報文類型*/,0x02/*剩余長度*/, (msg_id & 0xff00)>>8 , msg_id & 0x00ff/*最后兩個字節(jié)報文標(biāo)識符*/}; send_msg(sockfd,qos2_pubcomp_respon,sizeof(qos2_pubcomp_respon)); }

16.3.7 PINGREQ-心跳請求

客戶端發(fā)送 PINGREQ 報文給服務(wù)端的。用于:

a) 在沒有任何其它控制報文從客戶端發(fā)給服務(wù)的時,告知服務(wù)端客戶端還活著。

b) 請求服務(wù)端發(fā)送 響應(yīng)確認(rèn)它還活著。

c) 使用網(wǎng)絡(luò)以確認(rèn)網(wǎng)絡(luò)連接沒有斷開。

16.3.7.1 固定報頭

bit 7 6 5 4 3 2 1 0
Byte 1 MQTT報文類型(12) 保留位
1 1 0 0 0 0 0 0
Byte2 剩余長度 0

可變報頭

報文沒有可變報頭。

有效載荷

PINGREQ 報文沒有有效載荷。

16.3.7.2 PINGREQ 抓包報文

poYBAGGzOuWAW5i7AAAvOJBfs98472.png

圖 3.10 PINGREQ抓包報文圖示

16.3.7.3 c語言構(gòu)造pingreq報文

int mqtt_ping(int sockfd) { uint8 packet[] = {MQTT_MSG_PINGREQ/*報文類型*/,0x00/*剩余長度*/}; int ret = send_msg(sockfd,packet, sizeof(packet)); return ret; }

16.3.8 PINGRESP – 心跳響應(yīng)

服務(wù)端發(fā)送 PINGRESP 報文響應(yīng)客戶端的 PINGREQ 報文。 表示服務(wù)端還活著。

16.3.8.1 固定報頭

bit 7 6 5 4 3 2 1 0
Byte 1 MQTT報文類型(13) 保留位
1 1 0 1 0 0 0 0
Byte2 剩余長度 0

可變報頭

報文沒有可變報頭。

有效載荷

PINGRESP 報文沒有有效載荷。

16.3.8.2 PINGRESP 抓包報文

pYYBAGGzOuaACWYzAAAtcUGorAk127.png

圖 3.11 PINGRESP抓包報文圖示

16.3.8.3 c語言構(gòu)造pinpresp報文

void mqtt_ping_req_reply(int sockfd) { uint8_t cmd[]={0xd0/*報文類型*/, 0x00/*剩余長度*/}; send_msg(sockfd,cmd,sizeof(cmd)); }

16.3.9 DISCONNECT –斷開連接

DISCONNECT 報文是客戶端發(fā)給服務(wù)端的最后一個控制報文。表示客戶端正常斷開連接。

16.3.9.1 固定報頭

bit 7 6 5 4 3 2 1 0
Byte 1 MQTT報文類型(14) 保留位
1 1 1 0 0 0 0 0
Byte2 剩余長度 0

可變報頭

DISCONNECT報文沒有可變報頭。

有效載荷

DISCONNECT 報文沒有有效載荷。

16.3.9.2 DISCONNECT 抓包報文

poYBAGGzOuaAcWyMAAAc4e9-WMM369.png

圖 3.12 DISCONNECT抓包報文圖示

16.3.9.3 c語言構(gòu)造disconnect報文

int mqtt_disconnect(int sockfd) { uint8 packet[] = {MQTT_MSG_DISCONNECT/*報文類型*/,0x00/*剩余長度*/}; int ret = client_send(sockfd,packet, sizeof(packet)); DEBUG_INFO("ret=%d",ret); return ret; }

16.3.10 SUBSCRIBE-訂閱主題

客戶端向服務(wù)端發(fā)送 SUBSCRIBE 報文用于創(chuàng)建一個或多個訂閱。 每個訂閱注冊客戶端關(guān)心的一個或多個主題。 為了將應(yīng)用消息轉(zhuǎn)發(fā)給與那些訂閱匹配的主題, 服務(wù)端發(fā)送 PUBLISH 報文給客戶端。 SUBSCRIBE報文也(為每個訂閱)指定了最大的 QoS 等級, 服務(wù)端根據(jù)這個發(fā)送應(yīng)用消息給客戶端。

pYYBAGGzOuaAed3BAABoUAaCGJQ640.png

圖 3.13 訂閱主題報文組成格式

16.3.10.1 固定報頭

bit 7 6 5 4 3 2 1 0
Byte 1 MQTT報文類型(8) 保留位
1 1 1 0 0 0 0 0
Byte2 剩余長度 0

SUBSCRIBE 控制報固定報頭的第 3,2,1,0 位是保留位, 必須分別設(shè)置為 0,0,1,0,服務(wù)端必須將其它的任何值都當(dāng)做是不合法的并關(guān)閉網(wǎng)絡(luò)連接。

剩余長度字段
等于可變報頭的長度( 2 字節(jié)) 加上有效載荷的長度。

可變報頭

bit 7 6 5 4 3 2 1 0
Byte1 報文標(biāo)識符MSB
Byte2 報文標(biāo)識符LSB

服務(wù)端收到客戶端發(fā)送的一個 SUBSCRIBE 報文時, 必須使用 SUBACK 報文響應(yīng)。SUBACK 報文必須和等待確認(rèn)的 SUBSCRIBE 報文有相同的報文標(biāo)識符。

有效載荷

7 6 5 4 3 2 1 0
主題
Byte1 主題長度MSB
Byte2 主題長度LSB
Byte3~n 主題
服務(wù)質(zhì)量
保留 qos等級
ByteN+1 0 0 0 0 0 0 x x

QoS 不等于 0,1 或 2, 服務(wù)端必須認(rèn)為 SUBSCRIBE 報文是不合法的并關(guān)閉網(wǎng)絡(luò)連接。

16.3.10.2 SUBSCRIBE報文抓包

poYBAGGzOueAECCgAABX9ua5mis828.png

圖 3.14 訂閱主題抓包報文

16.3.10.3 c語言構(gòu)造subscribe報文

static uint16 su_seq = 1; int mqtt_subscribe_theme(int sockfd,char *Theme , uint8_t Qos) { su_seq++;//報文標(biāo)識符 if(su_seq == 0){ su_seq = 1; } uint16_t MessageId = su_seq; uint8_t cmd[1024]={0}; //報文標(biāo)示符長度2 + 主題長度位占用2字節(jié)+主題內(nèi)容+qos標(biāo)識 int data_length = 2+2+strlen(Theme)+1; int playload_len = strlen(Theme); uint8_t len_byte[4] ={0x00 , 0x00 ,0x00 ,0x00}; uint8_t byte_num = length_trans_byte_form(data_length , len_byte);/*把剩余長度轉(zhuǎn)換成變長編碼*/ cmd[0] = 0x82; memcpy(&cmd[1] , len_byte , byte_num); cmd[1+byte_num]=(MessageId & 0xff00) >> 8 ; cmd[1+byte_num+1] = MessageId & 0x00ff; cmd[1+byte_num+1+1] = (playload_len & 0xff00) >> 8; cmd[1+byte_num+1+1+1] = playload_len & 0x00ff; memcpy(&cmd[1+byte_num+1+1+1+1] , Theme , playload_len); cmd[1+byte_num+1+1+1+1+playload_len] = Qos; client_send(sockfd,cmd, 1+byte_num+1+1+1+1+playload_len+1); }

16.3.11 SUBACK –訂閱確認(rèn)

服務(wù)端發(fā)送 SUBACK 報文給客戶端,用于確認(rèn)它已收到并且正在處理 SUBSCRIBE 報文。

16.3.11.1 固定報頭

bit 7 6 5 4 3 2 1 0
Byte 1 MQTT報文類型(9) 保留位
1 0 0 1 0 0 0 0
Byte2 剩余長度 0

剩余長度字段
等于可變報頭的長度加上有效載荷的長度。

可變報頭

bit 7 6 5 4 3 2 1 0
Byte1 報文標(biāo)識符MSB
Byte2 報文標(biāo)識符LSB

可變報頭包含等待確認(rèn)的 SUBSCRIBE 報文的報文標(biāo)識符。

3.11.2 SUBACK抓包報文

pYYBAGGzOueAaK09AABFOV_QNYg978.png

圖 3.15訂閱主題ack抓包報文

16.3.11.3 c語言構(gòu)造suback報文

void mqtt_subscribe_ack(int sockfd,const uint8 *buf) { uint16 msg_id = mqtt_parse_msg_id(buf);/*提取報文標(biāo)識符*/ uint8 qos = MQTTParseMessageQos(buf);/*提取報文qos*/ uint8 cmd[]={0x90,0x03/*剩余長度*/, (msg_id & 0xff00) >> 8, msg_id & 0x00ff,qos}; send_msg(sockfd,cmd,sizeof(cmd)); }

16.3.12 UNSUBSCRIBE –取消訂閱

客戶端發(fā)送 UNSUBSCRIBE 報文給服務(wù)端, 用于取消訂閱主題。

poYBAGGzOueAXTgkAABeCdXmxdQ126.png

圖 3.16取消訂閱主題報文結(jié)構(gòu)

16.3.12.1 固定報頭

bit 7 6 5 4 3 2 1 0
Byte 1 MQTT報文類型(10) 保留位
1 0 1 0 0 0 0 0
Byte2 剩余長度 0

UNSUBSCRIBE 報文固定報頭的第 3,2,1,0 位是保留位且必須分別設(shè)置為 0,0,1,0。 服務(wù)端必須認(rèn)為任何其它的值都是不合法的并關(guān)閉網(wǎng)絡(luò)連接。

剩余長度字段
等于可變報頭的長度,加上有效載荷的長度。

可變報頭

bit 7 6 5 4 3 2 1 0
Byte1 報文標(biāo)識符MSB
Byte2 報文標(biāo)識符LSB

可變報頭包含一個報文標(biāo)識符。

有效載荷

7 6 5 4 3 2 1 0
主題1
Byte1 主題長度MSB
Byte2 主題長度LSB
Byte3~n 主題
主題2

UNSUBSCRIBE 報文的有效載荷必須至少包含一個消息過濾器。 沒有有效載荷的 UNSUBSCRIBE 報文是違反協(xié)議的。

16.3.12.2 UNSUBSCRIBE抓包報文

pYYBAGGzOueAXDUPAABIi_K_mdk278.png

圖 3.17取消訂閱主題抓包報文

16.3.12.3 c語言構(gòu)造unsubscribe報文

static uint16 un_seq = 1; int mqtt_unsubscribe_theme(int sockfd,const char* topic) { un_seq++; if(un_seq == 0){ un_seq = 1; } uint16_t MessageId = un_seq; uint8_t cmd[1024]={0}; //報文標(biāo)示符長度2 + 主題長度位占用2字節(jié)+主題內(nèi)容+qos標(biāo)識 int data_length = 2+2+strlen(topic)+1; int playload_len = strlen(topic); uint8_t len_byte[4] ={0x00 , 0x00 ,0x00 ,0x00}; uint8_t byte_num = length_trans_byte_form(data_length , len_byte);/*剩余長度轉(zhuǎn)換成變長編碼*/ cmd[0] = 0xa2; memcpy(&cmd[1] , len_byte , byte_num); cmd[1+byte_num]=(MessageId & 0xff00) >> 8 ; cmd[1+byte_num+1] = MessageId & 0x00ff; cmd[1+byte_num+1+1] = (playload_len & 0xff00) >> 8; cmd[1+byte_num+1+1+1] = playload_len & 0x00ff; memcpy(&cmd[1+byte_num+1+1+1+1] , topic , playload_len); client_send(sockfd,cmd,1+byte_num+1+1+1+1+playload_len+1); return 1; }

16.3.13 UNSUBACK –取消訂閱確認(rèn)

服務(wù)端發(fā)送 UNSUBACK 報文給客戶端用于確認(rèn)收到 UNSUBSCRIBE 報文。

poYBAGGzOuiAQyKBAAA2dGXo_GI317.png

圖 3.18取消訂閱主題ack報文組成

16.3.13.1 固定報頭

bit 7 6 5 4 3 2 1 0
Byte 1 MQTT報文類型(11) 保留位
1 0 1 1 0 0 0 0
Byte2 剩余長度 0

剩余長度字段
表示可變報頭的長度, 對 UNSUBACK 報文這個值等于 2。

可變報頭

bit 7 6 5 4 3 2 1 0
Byte1 報文標(biāo)識符MSB
Byte2 報文標(biāo)識符LSB

可變報頭包含等待確認(rèn)的 UNSUBSCRIBE 報文的報文標(biāo)識符。

16.3.12.2 UNSUBSCRIBE ACK抓包報文

pYYBAGGzOuiAVQbAAAAtHofI4HE667.png

圖 3.19取消訂閱主題ACK抓包報文

16.3.12.3 c語言構(gòu)造unsubscribe報文

void mqtt_unsubscribe_ack(int sockfd,const uint8 *buf) { uint16 msg_id = mqtt_parse_msg_id(buf); uint8 cmd[]={0xb0,0x02/*剩余長度*/,(msg_id & 0xff00) >> 8, msg_id & 0x00ff/*最后兩個字節(jié)是報文標(biāo)識符*/}; send_msg(sockfd,cmd,sizeof(cmd)); }

16.3.14 服務(wù)端與客戶端交互操作過程

16.3.14.1 編譯

編譯client之前先在代碼中指定server ip

poYBAGGzOumAZQ5DAAAhVOJZDk4261.png

進入client目錄,直接make即可

pYYBAGGzOumAXhcCAAA5CTEXKyA175.png

進入server目錄,直接make即可

poYBAGGzOuqAaJE5AAA6FHswyt0142.png

16.3.14.2 執(zhí)行

先運行server

pYYBAGGzOuqAC_LIAAAmGnNxgxY363.png

再運行client

poYBAGGzOuuAJmRCAABB_76Atiw904.png

Client操作流程

pYYBAGGzOuuAAAqkAADHpC_fbpw046.png

在server端查看

poYBAGGzOuyACXD0AAC6PH2v9V4367.png



審核編輯黃昊宇

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11123

    瀏覽量

    207900
  • MQTT
    +關(guān)注

    關(guān)注

    5

    文章

    629

    瀏覽量

    22286
收藏 人收藏

    評論

    相關(guān)推薦

    Linux 驅(qū)動開發(fā)與應(yīng)用開發(fā),你知道多少?

    一、Linux驅(qū)動開發(fā)與應(yīng)用開發(fā)的區(qū)別開發(fā)層次不同:Linux驅(qū)動開發(fā)主要是針對硬件設(shè)備進行編程
    的頭像 發(fā)表于 08-30 12:16 ?159次閱讀
    <b class='flag-5'>Linux</b> 驅(qū)動<b class='flag-5'>開發(fā)</b>與應(yīng)用<b class='flag-5'>開發(fā)</b>,你知道多少?

    紫光同芯亮相第十六屆汽車動力系統(tǒng)技術(shù)年會

    7月4-5日,第十六屆汽車動力系統(tǒng)技術(shù)年會(TMC2024)在青島盛大召開,活動匯聚來自全球各地的頂尖專家學(xué)者、行業(yè)精英以及各大汽車制造企業(yè)、頭部零部件企業(yè)的代表,與會者們圍繞汽車動力系統(tǒng)技術(shù)
    的頭像 發(fā)表于 07-08 15:02 ?484次閱讀

    文檔更新 |迅為 RK3568開發(fā)板驅(qū)動指南-第十五/十六

    《iTOP-RK3568開發(fā)板驅(qū)動開發(fā)指南》更新,本次更新內(nèi)容對應(yīng)的是驅(qū)動(第十五篇 i2C)(第十六篇 SPI)視頻,后續(xù)資料會不斷更新,不斷完善,幫助用戶快速入門,大大提升研發(fā)速度
    發(fā)表于 07-08 11:04

    廣明源將攜高質(zhì)量紫外光技術(shù)亮相第十六屆上海國際水展

    第十六屆上海國際水展將于2024年6月3-5日在上海國際會展中心開展,本屆展會規(guī)模達190000㎡,覆蓋市政、工業(yè)、凈水三大領(lǐng)域,預(yù)計2500家水處理行業(yè)品牌企業(yè)參展,并吸引全球100多個國家超10萬名海內(nèi)外客商共襄盛會。
    的頭像 發(fā)表于 05-30 14:44 ?261次閱讀
    廣明源將攜高質(zhì)量紫外光技術(shù)亮相<b class='flag-5'>第十六</b>屆上海國際水展

    6月17相約首都之窗丨第十六屆中國國際機床工具展覽會!

    第十六屆中國國際機床工具展覽會(CIMES?2024)定于6月17至21日在北京中國國際展覽中心(順義館)隆重舉辦。臺灣高技受邀參展!
    的頭像 發(fā)表于 05-20 17:44 ?356次閱讀
    6月17相約首都之窗丨<b class='flag-5'>第十六</b>屆中國國際機床工具展覽會!

    第十六屆中國國際電梯展覽會丨艾而特與多摩川攜手亮相,共創(chuàng)新高!

    2024年5月11日第十六屆中國國際電梯展覽會圓滿落幕點擊觀看精彩瞬間活動介紹作為最具規(guī)模和影響力的電梯行業(yè)盛會之一,第十六屆中國國際電梯展覽會是中國電梯行業(yè)的重要盛會,也是全球電梯產(chǎn)業(yè)的一次盛大
    的頭像 發(fā)表于 05-18 08:28 ?195次閱讀
    <b class='flag-5'>第十六</b>屆中國國際電梯展覽會丨艾而特與多摩川攜手亮相,共創(chuàng)新高!

    吹田電氣亮相第十六屆重慶國際電池展,技術(shù)盛宴完美落幕!

    為期三天的第十六屆重慶國際電池技術(shù)交流會/展覽會(CIBF2024)于4月29日在重慶國際博覽中心圓滿落幕。CIBF2024首次走進川渝,展會面積近18萬+平米,14個展館,預(yù)計20萬+觀眾參觀
    的頭像 發(fā)表于 04-30 08:33 ?261次閱讀
    吹田電氣亮相<b class='flag-5'>第十六</b>屆重慶國際電池展,技術(shù)盛宴完美落幕!

    鴻蒙OS開發(fā)實戰(zhàn):【ArkTS 實現(xiàn)MQTT協(xié)議(2)】

    1. 協(xié)議傳輸通道僅為TCPSocket 2. 基于HarmonyOS SDK API 9開發(fā) 3. 開發(fā)語言:ArkTS,TypeScript
    的頭像 發(fā)表于 04-01 14:48 ?1301次閱讀
    鴻蒙OS<b class='flag-5'>開發(fā)</b>實戰(zhàn):【ArkTS 實現(xiàn)<b class='flag-5'>MQTT</b><b class='flag-5'>協(xié)議</b>(2)】

    MQTT和Modbus的物聯(lián)網(wǎng)網(wǎng)關(guān)協(xié)議的區(qū)別分析

    MQTT和Modbus的物聯(lián)網(wǎng)網(wǎng)關(guān)協(xié)議區(qū)別分析 MQTT(Message Queuing Telemetry Transport)與Modbus是兩種廣泛應(yīng)用在物聯(lián)網(wǎng)環(huán)境中的通信
    的頭像 發(fā)表于 03-18 16:50 ?607次閱讀
    <b class='flag-5'>MQTT</b>和Modbus的物聯(lián)網(wǎng)網(wǎng)關(guān)<b class='flag-5'>協(xié)議</b>的區(qū)別<b class='flag-5'>分析</b>

    什么是MQTTMQTT協(xié)議中的方法

    MQTT是一種輕量級消息傳遞協(xié)議,由IBM開發(fā),于1999年首次發(fā)布。它使用發(fā)布/訂閱模式并在設(shè)備、服務(wù)器和應(yīng)用程序之間轉(zhuǎn)換消息。
    的頭像 發(fā)表于 02-25 17:04 ?1127次閱讀
    什么是<b class='flag-5'>MQTT</b>?<b class='flag-5'>MQTT</b><b class='flag-5'>協(xié)議</b>中的方法

    MQTT物聯(lián)網(wǎng)平臺是什么?MQTT物聯(lián)網(wǎng)平臺的功能有哪些

    ,易于實現(xiàn),因此廣泛應(yīng)用于受限的環(huán)境,如機器與機器的通信(M2M)以及物聯(lián)網(wǎng)環(huán)境(IoT)。 MQTT物聯(lián)網(wǎng)平臺通過提供一種標(biāo)準(zhǔn)化的協(xié)議和平臺,實現(xiàn)了消息的可靠傳輸、設(shè)備連接管理、數(shù)據(jù)存儲和分析等功能,方便
    的頭像 發(fā)表于 01-24 09:23 ?940次閱讀

    【ELF 1開發(fā)板試用】+ 8.2 MQTT開發(fā)——連接騰訊云【原創(chuàng)】

    MQTT開發(fā)——連接騰訊云 前文我們基于文檔中提供的項目教程,復(fù)現(xiàn)ELF 1連接阿里云物聯(lián)網(wǎng)平臺,進行了MQTT開發(fā),復(fù)現(xiàn)后,筆者就想“為何不試試其它平臺”,抱著整活學(xué)習(xí)的心,騰訊云的
    發(fā)表于 12-15 23:10

    【ELF 1開發(fā)板試用】+ 8.1 MQTT開發(fā)——連接阿里云【項目復(fù)現(xiàn)】

    MQTT開發(fā)——連接阿里云【項目復(fù)現(xiàn)】 本文基于文檔中提供的項目教程,復(fù)現(xiàn)ELF 1連接阿里云物聯(lián)網(wǎng)平臺,進行MQTT開發(fā);記錄ELF1 個人物聯(lián)網(wǎng)
    發(fā)表于 12-15 22:00

    興元科技董事長于卓出席第十六屆中國工業(yè)論壇并發(fā)布自主開發(fā)產(chǎn)品“工業(yè)元宇宙博覽會”

    ? ? ?2023?年 11 月 22?日,興元科技董事長于卓于第十六屆中國工業(yè)論壇絲路峰會暨中國新工業(yè)博覽會上發(fā)布自主研發(fā)的工業(yè)元宇宙線上博覽會,并對元宇宙博覽會的研發(fā)背景、技術(shù)特點、應(yīng)用場
    的頭像 發(fā)表于 12-04 17:54 ?1009次閱讀
    興元科技董事長于卓出席<b class='flag-5'>第十六</b>屆中國工業(yè)論壇并發(fā)布自主<b class='flag-5'>開發(fā)</b>產(chǎn)品“工業(yè)元宇宙博覽會”

    木幾智能亮相第十六屆中國工業(yè)論壇絲路峰會暨中國新工業(yè)博覽會

    11月22日-24日,在西安舉辦的以“新工業(yè)·新動能·新格局”為主題的第十六屆中國工業(yè)論壇絲路峰會暨中國新工業(yè)博覽會圓滿落下帷幕。木幾智能受廣州政府邀請與多家智能裝備企業(yè)隨廣州展團亮相本次峰會。
    的頭像 發(fā)表于 12-03 09:19 ?490次閱讀
    木幾智能亮相<b class='flag-5'>第十六</b>屆中國工業(yè)論壇絲路峰會暨中國新工業(yè)博覽會