MQTT協(xié)議是一個面向物聯(lián)網(wǎng)應用的即時通信協(xié)議,使用TCP/IP提供網(wǎng)絡連接,能夠對負載內(nèi)容實現(xiàn)消息屏蔽傳輸,開銷小,可以有效降低網(wǎng)絡流量。MQTT協(xié)議適用于設備和平臺需要保持長連接的使用場景,MQTT特點在于可以實現(xiàn)設備間的消息單播以及組播,可以不依賴于其他服務(下發(fā)命令服務,推送服務等)實現(xiàn)讓設備以應用服務器的方式對真實設備進行管理和控制。
正因為MQTT協(xié)議擁有這些特點,現(xiàn)在成文了各個物聯(lián)網(wǎng)云平臺支持的最廣泛的協(xié)議,百度、阿里、亞馬遜、OneNet等國內(nèi)外物聯(lián)網(wǎng)云服務提供商均支持該協(xié)議,所以在做物聯(lián)網(wǎng)開發(fā)的過程中,有必要學習和了解一下該協(xié)議。接下來我們就以OneNET的MQTT接入?yún)f(xié)議為例,學習一下該協(xié)議個通訊。
硬件連接環(huán)境:麒麟座迷你開發(fā)板用STlink連接到PC的USB口
軟件開發(fā)環(huán)境:Keil MDK5.25編輯麒麟座mini開發(fā)板官方例程”6.ESP8266-MQTT_TYPE3-LED”
網(wǎng)絡環(huán)境:PC機以太網(wǎng)卡連接路由器接入互聯(lián)網(wǎng),Windows10無線網(wǎng)卡建立熱點,麒麟座開發(fā)板的ESP8266經(jīng)過熱點接入互聯(lián)網(wǎng)。
抓包工具:Wireshart抓取以太網(wǎng)卡的數(shù)據(jù)包,設置過濾條件為”ip.addr == 183.230.40.39”,只顯示與OneNET MQTT服務器通訊的數(shù)據(jù)包。
模擬器:simulate-device.exe,在PC上可以模擬嵌入式設備通訊。
參考文檔:OneNET官方MQTT文檔:”MQTT.docx”,MQTT中文文檔MQTT.PDF
交互過程:連接權鑒,數(shù)據(jù)上報,命令下發(fā),斷開連接。
一、連接權鑒
首先在修改官方例程中的參數(shù)信息,把WiFi名稱和密碼改成使用PC無線網(wǎng)卡模擬的熱點網(wǎng)絡,OneNET服務器的IP地址和端口號確保為”183.230.40.39”和”6002”,onenet.c中的PROID、DEVID、AUTH_INFO修改為項目中的真實值。
編譯并下載程序到麒麟座迷你開發(fā)板,在PC上使用Wireshark開始抓取以太網(wǎng)卡的數(shù)據(jù)包,設置過濾條件為”ip.addr == 183.230.40.39”,只顯示與OneNET MQTT服務器通訊的數(shù)據(jù)包。給麒麟座開發(fā)板上電,等待幾秒鐘后,就可以看到開發(fā)板與OneNET服務器通訊的數(shù)據(jù)包了。
數(shù)據(jù)包中前三幀為開發(fā)板與OneNET服務器建立TCP連接的三次握手信息,這個是開發(fā)板給ESP8266發(fā)送建立TCP連接指令后,ESP8266與服務器之間自動建立的。
數(shù)據(jù)包的第四幀至第五幀為麒麟座開發(fā)板項OneNET發(fā)送的鑒權信息和服務區(qū)應答。第六幀和第七幀是OneNET服務器返回的鑒權結果信息和ESP8266的應答。
在以上過程中,我們作為設備端開發(fā)人員,只需要了解第四幀的鑒權信息發(fā)送和第六幀的服務器鑒權結果返回就可以了。
接下來重點分析第四幀數(shù)據(jù),次幀數(shù)據(jù)總計有114字節(jié),去除以太網(wǎng)頭14字節(jié),IP頭20字節(jié),TCP頭20字節(jié),剩余的TCP有效載荷共計60字節(jié)。
根據(jù)MQTT報文協(xié)議中規(guī)定,每一個MQTT包總共包含三部分:
1、Fixed Header部分定義如下:
根據(jù)抓包的數(shù)據(jù),TCP負載的第一個字節(jié)是0x10,對應表格可以得知,MQTT Packet Type值為1,名稱為CONNECT,其功能是客戶端請求與服務器建立連接。其第二個字節(jié)的0x3a為表格中的Remaining Length字段,為數(shù)據(jù)包的長度。
根據(jù)MQTT協(xié)議規(guī)定,剩余長度(Remaining Length)表示當前報文剩余部分的字節(jié)數(shù),包括可變報頭和負載的數(shù)據(jù)。剩余長度不包括用于編碼剩余長度字段本身的字節(jié)數(shù)。0x3a為十進制的58,這個數(shù)正好是TCP負載的60字節(jié)減去固定報頭的兩個字節(jié)長度。至于如何判斷剩余長度占用的字節(jié)數(shù),MQTT協(xié)議是這么規(guī)定的:
剩余長度字段使用一個變長度編碼方案,對小于128的值它使用單字節(jié)編碼。更大的值按下面的方式處理。低7位有效位用于編碼數(shù)據(jù),最高有效位用于指示是否有更多的字節(jié)。因此每個字節(jié)可以編碼128個數(shù)值和一個延續(xù)位(continuation bit)。剩余長度字段最大4個字節(jié)。
根據(jù)以上定義,0x3a的二進制最高位為0,可以判定數(shù)據(jù)長度為1字節(jié)。
固定報頭的部分分析完成后,根據(jù)下表進行判斷:
2、CONNECT類型的消息是有可變報頭和負載的。對于可變報頭部分,按照以下格式編碼:
? ? ? ??對照抓包數(shù)據(jù):
其中byte1-byte6是固定值,表格與數(shù)據(jù)完全對應。Byte7表示MQTT協(xié)議版本,這個必須固定為4,即3.1.1版,OneNET只支持這一版本協(xié)議,不支持更早版本的協(xié)議。
在Byte8中,user flag與password flag平臺不允許匿名登陸,因此這兩個標志位在連接時必須設置為1,否則認為協(xié)議錯誤,平臺將會斷開連接。所以該字節(jié)數(shù)據(jù)為0xC0。
Byte9-10為保持連接(Keep Alive),是一個以秒為單位的時間間隔,表示為一個16位的字,它是指在客戶端傳輸完成一個控制報文的時刻到發(fā)送下一個報文的時刻,兩者之間允許空閑的最大時間間隔。如果保持連接的值非零,并且服務端在一點五倍的保持連接時間內(nèi)沒有收到客戶端的控制報文,它必須斷開客戶端的網(wǎng)絡連接,認為網(wǎng)絡連接已斷開。OneNET規(guī)定最短120秒,最長65535秒,這里設置的事0x0100,也就是256秒。
3、負載部分
負載部分的數(shù)據(jù)是按照以下格式編碼
? ? ??對照數(shù)據(jù):
0x0008為域一的字符串長度,這里是8字節(jié),內(nèi)容為ASCII碼的“31421353”,正好是源碼中DEVID,也就是設備ID(DeviceID);0x0006為域二的字符串長度,這里是6字節(jié),內(nèi)容為ASCII碼的“141215”,正好是源碼中PROID,也就是產(chǎn)品ID(ProduceID);0x001C為域三的字符串長度,這里是28字節(jié),內(nèi)容為ASCII碼的”dpsO9ruH0aTZublG9g5SvBtFSEQ=”,正好是源碼中AUTH_INFO,也就是產(chǎn)品ID(AuthInfo);
至此,上傳的鑒權信息就分析完畢了,服務器接收到鑒權信息后首先會有一個應答包,同時進行鑒權,鑒權完畢后會下發(fā)結果給客戶端:
鑒權結果同樣采用TCP傳輸,總共60個字節(jié),除去以太網(wǎng)頭、IP頭、TCP頭共計54字節(jié),還剩余60字節(jié),其中有效TCP負載為4字節(jié),其后面的兩個字節(jié)為TCP數(shù)據(jù)包需要四字節(jié)對齊所補充的無效數(shù)據(jù)。
服務器返回的鑒權結果同樣遵循MQTT包規(guī)則,首先是固定報頭,根據(jù)上文表格0x20表示服務器確認連接,0x02表示后面跟隨兩字節(jié)有效數(shù)據(jù),這里就是可變報頭了。
可變報頭規(guī)則如下:
根據(jù)返回的數(shù)據(jù)為0x0000,表示鑒權成功了。
二、數(shù)據(jù)上報
數(shù)據(jù)上報過程其實就是TCP通訊過程,每一次上報數(shù)據(jù)需要三幀,分別是數(shù)據(jù)上報,服務器確認,客戶端確認,其中只需要了解數(shù)據(jù)上報幀就可以了。
在數(shù)據(jù)上報幀中,總計有121字節(jié),除去以太網(wǎng)頭、IP頭、TCP頭共計54字節(jié),TCP有效字節(jié)數(shù)為67字節(jié)。
1、固定報頭
根據(jù)MQTT協(xié)議規(guī)定的上傳報文中的固定報頭格式如下:
對照抓取的數(shù)據(jù),TCP負載第一個字節(jié)0x32中的“3”表示上傳數(shù)據(jù)報文,其中的“2”表示QoS值為1。
根據(jù)MQTT協(xié)議中服務質(zhì)量定義表格如下:
對照表格,表示QoS值為1,即至少分發(fā)一次。
TCP負載的第二字節(jié)的0x41表示后面數(shù)據(jù)長度為65字節(jié)。
2、可變報頭
其報文格式如下:
對照抓取數(shù)據(jù):
0x0003為域一的兩字節(jié)字符串長度,這里為3個字節(jié),內(nèi)容為主題名,這里為ASCII碼的”$dp”,OneNET規(guī)定,”$dp”為系統(tǒng)上傳數(shù)據(jù)點的指令。
接下來的0x000a為報文標識符(PacketIdentifier),因為之前QoS值選用的1,所以這兩個字節(jié)在這里是必須的,固定為10,也就是0x000a。
3、負載
Payload包含真正的數(shù)據(jù)點內(nèi)容,支持的格式如下:
對照抓取的數(shù)據(jù):
負載的第一個字節(jié)為0x03,即Type=3,根據(jù)類型3的說明:
類型3說明后面?zhèn)€數(shù)據(jù)是JSON格式2的字符串,后面兩個字節(jié)0x0037表示字符串的長度為55。
最后的55個字節(jié)就是上傳的數(shù)據(jù)了,內(nèi)容為55個ASCII碼:
{"Red_Led":1,"Green_Led":1,"Yellow_Led":1,"Blue_Led":1}
這里表示上傳了代表Led燈狀態(tài)的四個數(shù)據(jù)流以及對應的值,OneNET服務器就會解析數(shù)據(jù)流并保存數(shù)據(jù)了。
三、命令下發(fā)
通過抓取數(shù)據(jù)包,命令下發(fā)過程需要四幀完成。
根據(jù)MQTT協(xié)議,四幀分別是服務器命令下發(fā),客戶端應答,客戶端命令回復,服務器端應答。所以我們只需要了解服務器命令下發(fā)幀和客戶端命令回復幀即可。
根據(jù)抓取到的數(shù)據(jù),服務器命令下發(fā)幀總計108字節(jié),其中TCP負載為54字節(jié)。
固定報頭中的0x30表示發(fā)布消息,0x34表示后續(xù)內(nèi)容有52字節(jié)。
可變報頭部分數(shù)據(jù)格式如下:
根據(jù)抓取數(shù)據(jù),0002a表示字符串長度為42字節(jié),字符串內(nèi)容為ASCII碼的”$creq/e8b6c9b6-225b-57dc-abaa-246ba58761d8”。其中”$creq”為系統(tǒng)下發(fā)指令標記,”/”為分隔符,后續(xù)的”e8b6c9b6-225b-57dc-abaa-246ba58761d8”為該條指令的uuid,uuid是通用唯一識別碼,用于識別該指令的唯一性。
最后的八個字節(jié)為MQTT數(shù)據(jù)報的負載部分,為真正的指令內(nèi)容,這里是”redled:0”,客戶端接收到該指令后控制led燈的亮滅。
四、斷開連接
MQTT協(xié)議的斷開連接沒有特殊的規(guī)定,只是遵循了TCP的斷開連接過程,主要就是雙方各自發(fā)送FIN標記的TCP包,并給對方確認,總共四幀數(shù)據(jù)完成。
五、總結
至此OneNET官方例程中的主要MQTT協(xié)議就分析完成了,其實除了分析過的外,還有其他交易存在,比如訂閱、取消訂閱、創(chuàng)建Topic、推送Topic、離線Topic等等,但是在例程中沒有用到,而且在也不是最常用的,所以這里沒有分析。通過分析發(fā)現(xiàn),MQTT協(xié)議非常的精煉,很適合作為物聯(lián)網(wǎng)的控制協(xié)議,而且通過分析,基本了解了MQTT協(xié)議的主要內(nèi)容,這對于下一步在各個不同平臺(Arduino,STM32,樹莓派,windows)通過MQTT協(xié)議接入OneNET云做了充分的準備。
評論