那曲檬骨新材料有限公司

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 特點(diǎn)

16.1.3 應(yīng)用

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

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

16.2.2 mqtt固定報(bào)頭

16.2.3 mqtt控制報(bào)文類型

16.2.4 標(biāo)記

16.2.5 剩余長度

16.2.5.1 示例

16.2.6 可變報(bào)頭

16.2.7 有效載荷

16.3 報(bào)文分析

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

16.3.1.1 connect固定報(bào)頭

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報(bào)文

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

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

16.3.2.1 固定報(bào)頭

16.3.2.2 可變報(bào)頭

16.3.2.3 CONNACK報(bào)文wireshark抓包分析

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

16.3.3 PUBLISH-發(fā)布消息

16.3.3.1 固定報(bào)頭

16.3.3.2 抓包分析PUBLISH報(bào)文

16.3.3.3 構(gòu)造publish 報(bào)文

16.3.4 PUBREC-發(fā)布收到

16.3.4.1 固定報(bào)頭

16.3.4.2 PUBREC抓包報(bào)文

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

16.3.5 PUBREL-發(fā)布釋放

16.3.5.1 固定報(bào)頭

16.3.5.2 PUBREL抓包報(bào)文

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

16.3.6 PUBCOMP-發(fā)布完成

16.3.6.1 固定報(bào)頭

16.3.6.2 PUBCOMP抓包報(bào)文

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

16.3.7 PINGREQ-心跳請求

16.3.7.1 固定報(bào)頭

16.3.7.2 PINGREQ 抓包報(bào)文

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

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

16.3.8.1 固定報(bào)頭

16.3.8.2 PINGRESP 抓包報(bào)文

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

16.3.9 DISCONNECT –斷開連接

16.3.9.1 固定報(bào)頭

16.3.9.2 DISCONNECT 抓包報(bào)文

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

16.3.10 SUBSCRIBE-訂閱主題

16.3.10.1 固定報(bào)頭

16.3.10.2 SUBSCRIBE報(bào)文抓包

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

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

16.3.11.1 固定報(bào)頭

3.11.2 SUBACK抓包報(bào)文

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

16.3.12 UNSUBSCRIBE –取消訂閱

16.3.12.1 固定報(bào)頭

16.3.12.2 UNSUBSCRIBE抓包報(bào)文

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

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

16.3.13.1 固定報(bào)頭

16.3.12.2 UNSUBSCRIBE ACK抓包報(bào)文

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

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

16.1.2 特點(diǎn)

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

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

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

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

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

16.1.3 應(yīng)用

MQTT協(xié)議廣泛應(yīng)用于物聯(lián)網(wǎng)、移動(dò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) 移動(dòng)即時(shí)消息,例如Facebook Messenger

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

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

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

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

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

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

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

pYYBAGGzOreAP0HeAABZHrWeVHA190.png

圖2.1 mqtt報(bào)文組成

16.2.2 mqtt固定報(bào)頭

每個(gè) MQTT 控制報(bào)文都包含一個(gè)固定報(bào)頭, 固定報(bào)頭的格式如下圖:

poYBAGGzOreALMsLAABLE738lek049.png

圖2.2 mqtt固定報(bào)頭

16.2.3 mqtt控制報(bào)文類型

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

MQTT 控制報(bào)文的類型:如下表:

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

16.2.4 標(biāo)記

固定報(bào)頭第 1 個(gè)字節(jié)的剩余的 4 位 [3-0]包含每個(gè) MQTT 控制報(bào)文類型特定的標(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
控制報(bào)文 固定報(bào)頭標(biāo)志 Bit 3 Bit 2 Bit 1 Bit 0

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

16.2.5 剩余長度

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

pYYBAGGzOreALHPfAABbPCs-lGo988.png

圖2.3 剩余長度包含的報(bào)文范圍

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

例如, 十進(jìn)制數(shù) 64 會被編碼為一個(gè)字節(jié), 數(shù)值是 64, 十六進(jìn)制表示為 0x40,。十進(jìn)制數(shù)字321(=65+2*128)被編碼為兩個(gè)字節(jié), 最低有效位在前。 第一個(gè)字節(jié)是 65+128=193。 注意最高位為
1 表示后面至少還有一個(gè)字節(jié)。 第二個(gè)字節(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 可變報(bào)頭

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

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

poYBAGGzOriAUoMcAAAvI1lyyC8134.png

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

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

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

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

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

poYBAGGzOrmATGuUAAAbUxUPFwg557.png

圖2.5 需要 Packet Identifier 的報(bào)文類型交互示意圖

16.2.7 有效載荷

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

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

16.3 報(bào)文分析

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

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

pYYBAGGzOrqAOrw9AABm3d75Hww292.png

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

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

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

poYBAGGzOrqAdWzrAAD4p-euB-8009.png

圖3.2 connect報(bào)文組成

16.3.1.1 connect固定報(bào)頭

bit 7 6 5 4 3 2 1 0
Byte1 Mqtt報(bào)文類型(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ā)送一個(gè)返回碼為 0x01(不支持的協(xié)議級別)的CONNACK 報(bào)文響應(yīng)CONNECT 報(bào)文, 然后斷開客戶端的連接。

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清除會話

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

Bit2遺囑標(biāo)志

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

Bit3和 bit4遺囑 QoS

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

Bit5遺囑保留

如果遺囑消息被發(fā)布時(shí)需要保留,需要指定這一位的值, 如果遺囑標(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) 是一個(gè)以秒為單位的時(shí)間間隔,表示為一個(gè) 16 位的字,它是指在客戶端傳輸完成。

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

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

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

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

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

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

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

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

16.3.1.7 遺囑主題

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

16.3.1.8 遺囑消息

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

16.3.1.9 用戶名和密碼

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

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

pYYBAGGzOrqAEGAWAAA_goSeZQQ703.png

圖3.2 用戶名和密碼在connect報(bào)文中的組成

16.3.10.1 wirshark抓包分析connect報(bào)文

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

poYBAGGzOruALhghAAFZfL_zoXE234.png

圖 3.3使用wireshark抓包分析connect報(bào)文組成格式

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

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;/*保持連接字段,占用兩個(gè)字節(jié)*/ var_header[9] = KEEP_ALIVE&0xFF; remainLen = sizeof(var_header)+payload_len; /*剩余長度,也就是可變報(bào)頭加上負(fù)載的長度*/ if (remainLen > 127) { fixedHeaderSize++;// add an additional byte for Remaining Length } fixed_header = (uint8 *)malloc(fixedHeaderSize); /*固定報(bào)頭*/ // Message Type *fixed_header = MQTT_MSG_CONNECT;/*報(bào)文類型,connect*/ if (remainLen <= 127) {// Remaining Length,剩余長度計(jì)算,可變長編碼 *(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;/*固定報(bào)頭+可變報(bào)頭+負(fù)載長度*/ packet = (uint8 *)malloc(packet_length);/*分配內(nèi)存*/ memset(packet, 0, packet_length); memcpy(packet, fixed_header, fixedHeaderSize);/*填充固定報(bào)頭*/ free(fixed_header); offset += fixedHeaderSize; memcpy(packet+offset, var_header, sizeof(var_header));/*填充可變報(bào)頭*/ 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 報(bào)文響應(yīng)從客戶端收到的 CONNECT 報(bào)文。服務(wù)端發(fā)送給客戶端的第一個(gè)報(bào)文必須是 CONNACK。

16.3.2.1 固定報(bào)頭

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

剩余長度字段表示可變報(bào)頭的長度。 對于 CONNACK 報(bào)文這個(gè)值等于 2。

16.3.2.2 可變報(bào)頭

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

連接返回碼

如果服務(wù)端發(fā)送了一個(gè)包含非零返回碼的 CONNACK 報(bào)文, 那么它必須關(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報(bào)文wireshark抓包分析

pYYBAGGzOsGAHt38AABY_FbZl6c369.png

圖3.4 CONNACK 抓包報(bào)文

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

void mqtt_connect_ack(int sockfd) { uint8_t cmd[]={ 0x20/*報(bào)文類型*/, 0x02/*剩余長度*/ ,0x00,0x00/*最后兩個(gè)字節(jié)可變報(bào)頭表示返回狀態(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 控制報(bào)文是指從客戶端向服務(wù)端或者服務(wù)端向客戶端傳輸一個(gè)應(yīng)用消息。

poYBAGGzOseAVCQtAABkswXSRKs395.png

圖 3.5 publish報(bào)文組成格式

16.3.3.1 固定報(bào)頭

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

Bit3 dup

如果 DUP 標(biāo)志被設(shè)置為 0, 表示這是客戶端或服務(wù)端第一次請求發(fā)送這個(gè) PUBLISH 報(bào)文。 如果 DUP 標(biāo)志被設(shè)置為 1,表示這可能是一個(gè)早前報(bào)文請求的重發(fā)。客戶端或服務(wù)端請求重發(fā)一個(gè) PUBLISH 報(bào)文時(shí), 必須將 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報(bào)文交互流程

Bit0保留標(biāo)記位

一般設(shè)置為0。

剩余長度

等于可變報(bào)頭的長度加上有效載荷的長度。

可變報(bào)頭

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

16.3.3.2 抓包分析PUBLISH報(bào)文

poYBAGGzOtOAMC2WAACMX12OT68100.png

圖 3.6 PUBLISH 抓包報(bào)文

16.3.3.3 構(gòu)造publish 報(bào)文

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的報(bào)文需要填充標(biāo)識符,有點(diǎn)像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);/*固定報(bào)頭+剩余長度*/ // Message Type, DUP flag, QoS level, Retain *fixed_header = MQTT_MSG_PUBLISH | qos_flag;/*報(bào)文類型和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;/*固定報(bào)頭+可變報(bào)頭+負(fù)載長度*/ packet = (uint8 *)malloc(packet_length); memset(packet, 0, packet_length); memcpy(packet, fixed_header, fixedHeaderSize);/*填充固定報(bào)頭*/ memcpy(packet+fixedHeaderSize, var_header, var_headerSize);/*填充可變報(bào)頭*/ 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 報(bào)文是對 QoS 等級 2 的 PUBLISH 報(bào)文的響應(yīng)。它是 QoS 2 等級協(xié)議交換的第二個(gè)報(bào)文。

16.3.4.1 固定報(bào)頭

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

剩余長度

表示可變報(bào)頭的長度。 對 PUBREC 報(bào)文它的值等于 2。

可變報(bào)頭

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

有效載荷

PUBREC 報(bào)文沒有有效載荷。

16.3.4.2 PUBREC抓包報(bào)文

pYYBAGGzOtiACPMOAAA3fmTL1oQ105.png

圖 3.7 PUBREC抓包報(bào)文圖示

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

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

16.3.5 PUBREL-發(fā)布釋放

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

16.3.5.1 固定報(bào)頭

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

剩余長度

表示可變報(bào)頭的長度。 對 PUBREL 報(bào)文它的值等于 2。

可變報(bào)頭

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

有效載荷

PUBREL 報(bào)文沒有有效載荷。

16.3.5.2 PUBREL抓包報(bào)文

poYBAGGzOt6ASFeNAAA09LDGivU386.png

圖 3.8 PUBREL抓包報(bào)文圖示

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

//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/*報(bào)文類型*/,0x02/*剩余長度*/, (msg_id & 0xff00)>>8 , msg_id & 0x00ff/*最后兩個(gè)字節(jié)是報(bào)文標(biāo)識符*/}; send_msg(sockfd,qos2_pubrel_respon,sizeof(qos2_pubrel_respon)); }

16.3.6 PUBCOMP-發(fā)布完成

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

16.3.6.1 固定報(bào)頭

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

剩余長度

表示可變報(bào)頭的長度。 對 PUBCOMP 報(bào)文它的值等于 2。

可變報(bào)頭

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

有效載荷

PUBCOMP 報(bào)文沒有有效載荷。

16.3.6.2 PUBCOMP抓包報(bào)文

pYYBAGGzOuSACFmjAAA6NB9m4qk558.png

圖 3.9 PUBCOMP抓包報(bào)文圖示

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

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

16.3.7 PINGREQ-心跳請求

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

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

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

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

16.3.7.1 固定報(bào)頭

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

可變報(bào)頭

報(bào)文沒有可變報(bào)頭。

有效載荷

PINGREQ 報(bào)文沒有有效載荷。

16.3.7.2 PINGREQ 抓包報(bào)文

poYBAGGzOuWAW5i7AAAvOJBfs98472.png

圖 3.10 PINGREQ抓包報(bào)文圖示

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

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

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

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

16.3.8.1 固定報(bào)頭

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

可變報(bào)頭

報(bào)文沒有可變報(bào)頭。

有效載荷

PINGRESP 報(bào)文沒有有效載荷。

16.3.8.2 PINGRESP 抓包報(bào)文

pYYBAGGzOuaACWYzAAAtcUGorAk127.png

圖 3.11 PINGRESP抓包報(bào)文圖示

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

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

16.3.9 DISCONNECT –斷開連接

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

16.3.9.1 固定報(bào)頭

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

可變報(bào)頭

DISCONNECT報(bào)文沒有可變報(bào)頭。

有效載荷

DISCONNECT 報(bào)文沒有有效載荷。

16.3.9.2 DISCONNECT 抓包報(bào)文

poYBAGGzOuaAcWyMAAAc4e9-WMM369.png

圖 3.12 DISCONNECT抓包報(bào)文圖示

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

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

16.3.10 SUBSCRIBE-訂閱主題

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

pYYBAGGzOuaAed3BAABoUAaCGJQ640.png

圖 3.13 訂閱主題報(bào)文組成格式

16.3.10.1 固定報(bào)頭

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

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

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

可變報(bào)頭

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

服務(wù)端收到客戶端發(fā)送的一個(gè) SUBSCRIBE 報(bào)文時(shí), 必須使用 SUBACK 報(bào)文響應(yīng)。SUBACK 報(bào)文必須和等待確認(rèn)的 SUBSCRIBE 報(bào)文有相同的報(bào)文標(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 報(bào)文是不合法的并關(guān)閉網(wǎng)絡(luò)連接。

16.3.10.2 SUBSCRIBE報(bào)文抓包

poYBAGGzOueAECCgAABX9ua5mis828.png

圖 3.14 訂閱主題抓包報(bào)文

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

static uint16 su_seq = 1; int mqtt_subscribe_theme(int sockfd,char *Theme , uint8_t Qos) { su_seq++;//報(bào)文標(biāo)識符 if(su_seq == 0){ su_seq = 1; } uint16_t MessageId = su_seq; uint8_t cmd[1024]={0}; //報(bào)文標(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 報(bào)文給客戶端,用于確認(rèn)它已收到并且正在處理 SUBSCRIBE 報(bào)文。

16.3.11.1 固定報(bào)頭

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

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

可變報(bào)頭

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

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

3.11.2 SUBACK抓包報(bào)文

pYYBAGGzOueAaK09AABFOV_QNYg978.png

圖 3.15訂閱主題ack抓包報(bào)文

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

void mqtt_subscribe_ack(int sockfd,const uint8 *buf) { uint16 msg_id = mqtt_parse_msg_id(buf);/*提取報(bào)文標(biāo)識符*/ uint8 qos = MQTTParseMessageQos(buf);/*提取報(bào)文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 報(bào)文給服務(wù)端, 用于取消訂閱主題。

poYBAGGzOueAXTgkAABeCdXmxdQ126.png

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

16.3.12.1 固定報(bào)頭

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

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

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

可變報(bào)頭

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

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

有效載荷

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

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

16.3.12.2 UNSUBSCRIBE抓包報(bào)文

pYYBAGGzOueAXDUPAABIi_K_mdk278.png

圖 3.17取消訂閱主題抓包報(bào)文

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

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}; //報(bào)文標(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 報(bào)文給客戶端用于確認(rèn)收到 UNSUBSCRIBE 報(bào)文。

poYBAGGzOuiAQyKBAAA2dGXo_GI317.png

圖 3.18取消訂閱主題ack報(bào)文組成

16.3.13.1 固定報(bào)頭

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

剩余長度字段
表示可變報(bào)頭的長度, 對 UNSUBACK 報(bào)文這個(gè)值等于 2。

可變報(bào)頭

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

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

16.3.12.2 UNSUBSCRIBE ACK抓包報(bào)文

pYYBAGGzOuiAVQbAAAAtHofI4HE667.png

圖 3.19取消訂閱主題ACK抓包報(bào)文

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

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/*最后兩個(gè)字節(jié)是報(bào)文標(biāo)識符*/}; send_msg(sockfd,cmd,sizeof(cmd)); }

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

16.3.14.1 編譯

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

poYBAGGzOumAZQ5DAAAhVOJZDk4261.png

進(jìn)入client目錄,直接make即可

pYYBAGGzOumAXhcCAAA5CTEXKyA175.png

進(jìn)入server目錄,直接make即可

poYBAGGzOuqAaJE5AAA6FHswyt0142.png

16.3.14.2 執(zhí)行

先運(yùn)行server

pYYBAGGzOuqAC_LIAAAmGnNxgxY363.png

再運(yùn)行client

poYBAGGzOuuAJmRCAABB_76Atiw904.png

Client操作流程

pYYBAGGzOuuAAAqkAADHpC_fbpw046.png

在server端查看

poYBAGGzOuyACXD0AAC6PH2v9V4367.png



審核編輯黃昊宇

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

    關(guān)注

    87

    文章

    11345

    瀏覽量

    210376
  • MQTT
    +關(guān)注

    關(guān)注

    5

    文章

    653

    瀏覽量

    22689
收藏 人收藏

    評論

    相關(guān)推薦

    華礪智行亮相第十六屆中國(濟(jì)南)國際信息技術(shù)博覽會

    近日,為期三天的的第十六屆中國(濟(jì)南)國際信息技術(shù)博覽會在濟(jì)南黃河國際會展中心圓滿落幕。本次盛會匯聚行業(yè)精英,以“展覽+會議+大賽+場景體驗(yàn)”的辦會模式,全面展示了信息技術(shù)領(lǐng)域的最新成果與創(chuàng)新趨勢。
    的頭像 發(fā)表于 12-27 13:40 ?170次閱讀

    《DNESP32S3使用指南-IDF版_V1.6》 第十六章 WATCH_DOG實(shí)驗(yàn)

    第十六章 WATCH_DOG實(shí)驗(yàn) 本章,我們將學(xué)習(xí)看門狗,基于定時(shí)器功能,教大家如何用定時(shí)器模擬看門狗功能。本章分為如下幾個(gè)小節(jié):16.1 看門狗簡介16.2 硬件設(shè)計(jì)16.3 程序設(shè)計(jì)16.4
    發(fā)表于 12-17 09:20

    SPEA出席第十六屆在蘇意大利企業(yè)答謝交流會

    近日,中國意大利商會(CICC)在意大利駐上海總領(lǐng)事館及蘇州政府大力支持下,于蘇州工業(yè)園區(qū)香格里拉酒店舉辦了第十六屆在蘇意大利企業(yè)答謝交流會。包括SPEA在內(nèi)的一批知名意大利企業(yè)代表出席本次活動(dòng)
    的頭像 發(fā)表于 12-03 01:05 ?339次閱讀
    SPEA出席<b class='flag-5'>第十六</b>屆在蘇意大利企業(yè)答謝交流會

    美新半導(dǎo)體亮相第十六屆傳感器與MEMS產(chǎn)業(yè)化技術(shù)國際研討會

    第十六屆傳感器與MEMS產(chǎn)業(yè)化技術(shù)國際研討會是一個(gè)專注于傳感器與MEMS技術(shù)的國際性盛會,旨在推動(dòng)MEMS行業(yè)的交流與合作發(fā)展。美新半導(dǎo)體作為業(yè)界領(lǐng)軍企業(yè)參與此次研討會并與行業(yè)專家和同行進(jìn)行了深入的交流,分享MEMS技術(shù)的未來發(fā)展趨勢。
    的頭像 發(fā)表于 11-26 11:29 ?410次閱讀

    迅為iTOP-RK3568開發(fā)板驅(qū)動(dòng)開發(fā)指南-第十八篇 PWM

    第七期_設(shè)備樹 第八期_設(shè)備樹插件 第九期_設(shè)備模型 第十期_熱插拔第十一期_pinctrl子系統(tǒng) 第十二期_GPIO子系統(tǒng) 第十三期_輸入子系統(tǒng)
    發(fā)表于 10-29 10:13

    《DNK210使用指南 -CanMV版 V1.0》第十六章 machine模塊實(shí)驗(yàn)

    第十六章 machine模塊實(shí)驗(yàn) 本章實(shí)驗(yàn)將介紹machine模塊的基礎(chǔ)知識,以及machine模塊內(nèi)部分函數(shù)的使用。通過本章的學(xué)習(xí),讀者將學(xué)習(xí)到machine模塊的基本使用方法。本章分為如下幾個(gè)
    發(fā)表于 10-14 09:25

    博格華納亮相第十六屆汽車動(dòng)力系統(tǒng)技術(shù)年會

    2024年第十六屆汽車動(dòng)力系統(tǒng)技術(shù)年會(TMC2024)于日前在青島隆重召開。TMC深耕行業(yè)十五載,作為中國最大規(guī)模、最具影響力的新能源汽車關(guān)鍵技術(shù)交流平臺,致力于通過動(dòng)力系統(tǒng)的技術(shù)創(chuàng)新推動(dòng)汽車行業(yè)實(shí)現(xiàn)“雙碳”目標(biāo)。
    的頭像 發(fā)表于 09-24 15:57 ?435次閱讀

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

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

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

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

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

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

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

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

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

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

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

    1. 協(xié)議傳輸通道僅為TCPSocket 2. 基于HarmonyOS SDK API 9開發(fā) 3. 開發(fā)語言:ArkTS,TypeScript
    的頭像 發(fā)表于 04-01 14:48 ?1642次閱讀
    鴻蒙OS<b class='flag-5'>開發(fā)</b>實(shí)戰(zhàn):【ArkTS 實(shí)現(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 ?892次閱讀
    <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 ?1427次閱讀
    什么是<b class='flag-5'>MQTT</b>?<b class='flag-5'>MQTT</b><b class='flag-5'>協(xié)議</b>中的方法
    大丰收百家乐官网的玩法技巧和规则 | 现金百家乐官网网上娱乐| 大发888官方6222.| 皇冠百家乐赢钱皇冠| 盐城百家乐官网的玩法技巧和规则| 百家乐官网技巧发布| 澳门百家乐官网必胜看| 百家乐官网视频无法显示| 蒙特卡罗线上娱乐| 免费百家乐分析工具| 百家乐注册开户送彩金| 百家乐官网博彩网址| 百家乐官网经验博彩正网| 同城乐| 大发888游戏在线客服| 澳门百家乐打法百家乐破解方法 | 百家乐波音平台导航网| 巴黎人百家乐官网的玩法技巧和规则 | 水果机破解器| 最好的百家乐博彩网站| 火箭百家乐官网的玩法技巧和规则| 百家乐官网娱乐城返水| 明珠国际娱乐| 大发888游戏平台888| 百佬汇百家乐的玩法技巧和规则| 百家乐论坛百科| 678百家乐官网博彩赌场娱乐网规则| 百家乐官网视频游戏世界| 大富翁娱乐城| 大发888官方pt老虎机大咖炸金花网页扎金花| 百家乐游戏机技| 百家乐存在千术吗| 真人百家乐官网的玩法技巧和规则 | 百家乐官网视频看不到| 必博网| 大发888 有斗地主吗| 百家乐软件代理打| 真钱的棋牌游戏| 粤港澳百家乐赌场娱乐网规则| 太阳城百家乐出千技术| 百家乐投注科学公式|