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