重要提示:第二節內容中所有操作使用時需要把對應的產品、裝置證書等替換為個人對應的資料。為了方便讀者更方便直觀的瞭解協議報文形成過程,資料沒有隱藏和加密處理,但請勿直接使用文中資料,造成損失將進行法律追究。
1 MQTT協議概述
1.1 背景概述
MQTT最初是由IBM的Andy Stanford-Clark以及Arcom公司ArlenNipper 為解決石油和天然氣管道感測器資料傳輸問題而創造,他們透過衛星網路傳輸輸油管道感測器資料,當時衛星通訊存在費用昂貴、網路中斷、低頻寬、高延遲等特點,而且感測器數量很多,MQTT就由此而誕生。近年來,物聯網發展迅猛,感測器數量爆發式增長,mqtt也被重新拾起。
MQTT(Message Queuing Telemetry Transport,訊息佇列遙測傳輸協議),是一種基於釋出/訂閱(Publish/Subscribe)模式的輕量級通訊協議,該協議構建於TCP/IP協議上,由IBM在1999年釋出,目前最新版本為v3.1.1。MQTT最大的優點在於可以以極少的程式碼和有限的頻寬,為遠端裝置提供實時可靠的訊息服務。做為一種低開銷、低頻寬佔用的即時通訊協議,MQTT在物聯網、小型裝置、移動應用等方面有廣泛的應用。
1.2 協議概述
MQTT是一種基於訂閱和釋出模式的通訊方式,透過以主題為核心的方式進行資料或資訊的傳輸。MQTT協議幾個重要的關鍵詞:
- 主題(Topic):附加在應用訊息上的一個標籤,伺服器broker就是根據topic名稱與訂閱該topic的client進行關聯,然後進行訊息轉發。MQTT的Topic是一種層級結構,用'/'來標識不同的層級。
- 訂閱(Subscription):客戶端透過“訂閱”某個topic,當這個topic有新的訊息時,伺服器會自動將訊息轉發給這個client。
- 釋出(Topic):釋出既可以是客戶端也可以是服務端,但是最終都是需要服務端broker進行轉發。
- 會話(Session):從客戶端向服務端發起MQTT連線請求開始,到連線中斷,直到會話過期未知的訊息手法序列稱之為會話。
- 負載(Payload):訊息訂閱者所具體接收的內容。
- 訊息服務質量(QoS):MQTT有三種服務質量:QoS0、QoS1、QoS2,用以標記訊息傳送的次數。
- 保留訊息 Retained Messages和最後遺囑 Last Will & Testament
mqtt報文結構
mqtt有釋出、訂閱、連線、心跳、請求等報文,但基本結構都類似,報文結構如下:
固定報頭 +可變報頭+有效載荷
- 固定報頭:由訊息型別、標誌位、剩餘長度組成。主要作用是用以標識報文訊息型別、服務質量、重發標誌、RETAIN、剩餘長度等。每個 MQTT 控制報文都必須包含一個固定報頭。組成結構如下:
報文型別值有以下幾種:
- 可變報頭:主要由報文的一些識別符號型別組成,可變報頭的內容根據報文型別的不同而不同,不是所有報文都必須有這項。結構如下:不同報文需要可變報頭的情況如下:
- 有效載荷:有效載荷包含將被傳輸的應用訊息。資料的內容和格式是應用特定的,不同的報文型別格式也是不一樣的。該欄位並非所有報文都需要,常見的報文包含有效載荷情況如下:報文的具體格式不再詳細展開,下一節內容中選幾種報文並結合實際來了解,官方協議文件:中文版:https://note.youdao.com/s/K5dOMTUC英文版:https://note.youdao.com/s/1QLq6sfU
2 MQTT 協議詳解與IOT簡單應用
2.1 準備工作
本節中使用阿里雲的物聯網平臺,結合一下除錯工具來實現透過MQTT的通訊等操作。需要具備以下環境:
1、申請註冊阿里雲物聯網平臺,並建立產品、裝置,參考阿里雲操作手冊即可:https://help.aliyun.com/document_detail/30528.html
2、(可選)linux系統環境
3、工具軟體:mqtt.fx、網路除錯助手
2.2 基於阿里雲平臺的IOT應用體驗
阿里雲提供全面的物聯網連線服務,可以在其物聯網平臺做一些簡單的入門測試,官方有詳細的操作文件,不再贅述,參考操作指導:https://help.aliyun.com/document_detail/73708.html
以下為部分測試效果圖:
阿里雲物聯網平臺線上除錯
客戶端SDK demo程式執行
結合MQTT.fx測試
結合MQTT.fx測試
2.3 MQTT部分報文協議詳解與測試
為接近實際應用,本小節透過網路除錯助手和阿里雲平臺來更加詳細的分析MQTT的幾個重要報文。報文內容使用16進位制方式展示,需要特別注意一下報文內容有些採用UTF-8編碼,以及剩餘長度的編碼格式。
UTF-8 編碼字元對 ASCII 字元的編碼做了最佳化。每一個字串都有兩位元組的長度欄位作為字首,它標識了這個字串 UTF-8 編碼的位元組數。
剩餘長度(Remaining Length)表示當前報文剩餘部分的位元組數,包括可變報頭和負載的資料。剩餘長度 不包括用於編碼剩餘長度欄位本身的位元組數。剩餘長度欄位使用一個變長度編碼方案,對小於 128 的值它使用單位元組編碼。更大的值按下面的方式處理。低 7 位有效位用於編碼資料,最高有效位用於指示是否有更多的位元組。因此每個位元組可以編碼 128 個數值和一個 延續位( continuation bit ) 。剩餘長度欄位最大 4 個位元組。
2.3.1 CONNECT-連線報文
- MQTT連線報文格式:固定報頭+可變報頭+有效載荷 固定報頭:報文型別+保留位置+剩餘長度 固定報頭共兩個位元組,如下圖: 根據協議說明,得到連線報文固定報頭為: 10 ?? ??表示剩餘長度,留到最後再來填寫。
- 可變報頭:協議名稱+協議級別+連線標誌+儲存連線 可變報頭共10個位元組,如下圖: 根據協議說明,得到連線報文可變報頭為: 00 04 4D 51 54 54 04 C2 00 64
- 有效載荷:客戶端標識+使用者名稱稱+密碼 各個部分格式如下: 客戶端ID:|securemode=3,signmethod=hmacsha1| 另外一種客戶端格式#.|securemode=3,signmethod=hmacsha1|,timestamp=2524608000000| 使用者名稱:&# 密碼(需要經過雜湊加密):clientIddeviceName*productKeygnuyAXcWtth *:程式碼裝置名稱 #:代表裝置證書 ProductKey
得到連線報文的 有效載荷 過程如下: 1、代替 客戶端ID:IOT_F1-01|securemode=3,signmethod=hmacsha1| 使用者名稱:IOT_F1-01&gnuyAXcWtth 密碼:clientIdIOT_F1-01deviceNameIOT_F1-01productKeygnuyAXcWtth
2、轉16進位制
客戶端ID:49 4F 54 5F 46 31 2D 30 31 7C 73 65 63 75 72 65 6D 6F 64 65 3D 33 2C 73 69 67 6E 6D 65 74 68 6F 64 3D 68 6D 61 63 73 68 61 31 7C
使用者名稱:49 4F 54 5F 46 31 2D 30 31 26 67 6E 75 79 41 58 63 57 74 74 68
密碼(雜湊加密):a73628d4a216e570bdd3997767e827ef4ae836e7
密碼:61 37 33 36 32 38 64 34 61 32 31 36 65 35 37 30 62 64 64 33 39 39 37 37 36 37 65 38 32 37 65 66 34 61 65 38 33 36 65 37
3、utf-8編碼(加兩個位元組的字首表示長度) 客戶端ID:00 2B 49 4F 54 5F 46 31 2D 30 31 7C 73 65 63 75 72 65 6D 6F 64 65 3D 33 2C 73 69 67 6E 6D 65 74 68 6F 64 3D 68 6D 61 63 73 68 61 31 7C 使用者名稱:00 15 49 4F 54 5F 46 31 2D 30 31 26 67 6E 75 79 41 58 63 57 74 74 68 密碼:00 28 61 37 33 36 32 38 64 34 61 32 31 36 65 35 37 30 62 64 64 33 39 39 37 37 36 37 65 38 32 37 65 66 34 61 65 38 33 36 65 37 最終有效載荷: 00 2B 49 4F 54 5F 46 31 2D 30 31 7C 73 65 63 75 72 65 6D 6F 64 65 3D 33 2C 73 69 67 6E 6D 65 74 68 6F 64 3D 68 6D 61 63 73 68 61 31 7C 00 15 49 4F 54 5F 46 31 2D 30 31 26 67 6E 75 79 41 58 63 57 74 74 68 00 28 61 37 33 36 32 38 64 34 61 32 31 36 65 35 37 30 62 64 64 33 39 39 37 37 36 37 65 38 32 37 65 66 34 61 65 38 33 36 65 37
計算得到可變報頭加有效載荷的長度為120(0x78),把固定報頭的剩餘長度補充得到最終 連線報文: 10 78 00 04 4D 51 54 54 04 C2 00 64 00 2B 49 4F 54 5F 46 31 2D 30 31 7C 73 65 63 75 72 65 6D 6F 64 65 3D 33 2C 73 69 67 6E 6D 65 74 68 6F 64 3D 68 6D 61 63 73 68 61 31 7C 00 15 49 4F 54 5F 46 31 2D 30 31 26 67 6E 75 79 41 58 63 57 74 74 68 00 28 61 37 33 36 32 38 64 34 61 32 31 36 65 35 37 30 62 64 64 33 39 39 37 37 36 37 65 38 32 37 65 66 34 61 65 38 33 36 65 37
開啟軟體除錯助手,以TCP CLIENT的模式連線阿里雲平臺,其目的IP、埠為阿里雲的物聯網平臺連線引數,可以在平臺裝置的mqtt連線引數中找到地址和埠引數。把這個地址填到網路除錯助手連線即可。 連線後以hex方式傳送上面得到的連線報文內容,可以看到已經收到了伺服器返回的資訊。
2.3.2 CONNACK-連線確認報文(服務端下發)
連線確認報文格式:固定報頭+可變報頭 上述測試中,伺服器返回20 02 00 00,查詢MQTT協議的連線確認報文可知道伺服器接受了連線。 報文格式如下:
2.3.3 釋出訊息(PUBLISH)和釋出確認(PUBACK)
1、釋出訊息 釋出訊息的報文格式為:固定報頭+可變報頭+有效載荷+響應+動作
- 固定報頭有兩個位元組,定義如下: 其中QoS等級取值如下: 根據協議描述,把DUP置為0、RETAIN置為1,分別表示這是第一次請求傳送publish報文,而不是重發,RETAIN被設定為1,表示服務端必須儲存這個應用訊息和它的服務質量等級(QoS),以便它可以被分發給未來的主題名匹配的訂閱者;另外取QoS為0。 由此得到需要傳送的固定報頭為:31 ?? (??表示剩餘長度)
- 可變報頭: 可變報頭格式如下:主題名(topic)+報文標識 需要注意的是報文標識只有在服務質量等級大於0才使用。
主題在這裡使用阿里雲產品中的物模型topic,也可以使用自定義主題等。
(1)把deviceName替換為裝置名稱,得到可變報頭內容為: /sys/gnuyAXcWtth/IOT_F1-01/thing/event/property/post 轉為16進位制並左utf-8編碼得到可變報頭為: 00 34 2F 73 79 73 2F 67 6E 75 79 41 58 63 57 74 74 68 2F 49 4F 54 5F 46 31 2D 30 31 2F 74 68 69 6E 67 2F 65 76 65 6E 74 2F 70 72 6F 70 65 72 74 79 2F 70 6F 73 74
- 有效載荷:有效載荷包含將被髮布的應用訊息。資料的內容和格式是應用特定的。 在阿里雲中裝置的的物理模型資料有執行狀態、溫度、溼度,所以測試的有效載荷就傳送這幾個資料。 1、有效載荷內容: {"method":"thing.service.property.post","id":"00000001","params":{"RunningState":1,"Humidity":85,"temperature":26.6,},"version":"1.0.0"}
轉16進位制: 7B 22 6D 65 74 68 6F 64 22 3A 22 74 68 69 6E 67 2E 73 65 72 76 69 63 65 2E 70 72 6F 70 65 72 74 79 2E 70 6F 73 74 22 2C 22 69 64 22 3A 22 30 30 30 30 30 30 30 31 22 2C 22 70 61 72 61 6D 73 22 3A 7B 22 52 75 6E 6E 69 6E 67 53 74 61 74 65 22 3A 31 2C 22 48 75 6D 69 64 69 74 79 22 3A 38 35 2C 22 74 65 6D 70 65 72 61 74 75 72 65 22 3A 32 36 2E 36 2C 7D 2C 22 76 65 72 73 69 6F 6E 22 3A 22 31 2E 30 2E 30 22 7D
(2)這裡使用QoS為0,所以就不包含響應和動作兩個欄位,得到 固定報頭+可變報頭+有效載荷 內容: 31 ?? 00 34 2F 73 79 73 2F 67 6E 75 79 41 58 63 57 74 74 68 2F 49 4F 54 5F 46 31 2D 30 31 2F 74 68 69 6E 67 2F 65 76 65 6E 74 2F 70 72 6F 70 65 72 74 79 2F 70 6F 73 74 7B 22 6D 65 74 68 6F 64 22 3A 22 74 68 69 6E 67 2E 73 65 72 76 69 63 65 2E 70 72 6F 70 65 72 74 79 2E 70 6F 73 74 22 2C 22 69 64 22 3A 22 30 30 30 30 30 30 30 31 22 2C 22 70 61 72 61 6D 73 22 3A 7B 22 52 75 6E 6E 69 6E 67 53 74 61 74 65 22 3A 31 2C 22 48 75 6D 69 64 69 74 79 22 3A 38 35 2C 22 74 65 6D 70 65 72 61 74 75 72 65 22 3A 32 36 2E 36 2C 7D 2C 22 76 65 72 73 69 6F 6E 22 3A 22 31 2E 30 2E 30 22 7D 求得剩餘長度為:190(0xBE) 注意剩餘長度的表示規則,按規則處理後表示為:0xBE 0x01。
(3)最終得到的釋出報文為: 31 BE 01 00 34 2F 73 79 73 2F 67 6E 75 79 41 58 63 57 74 74 68 2F 49 4F 54 5F 46 31 2D 30 31 2F 74 68 69 6E 67 2F 65 76 65 6E 74 2F 70 72 6F 70 65 72 74 79 2F 70 6F 73 74 7B 22 6D 65 74 68 6F 64 22 3A 22 74 68 69 6E 67 2E 73 65 72 76 69 63 65 2E 70 72 6F 70 65 72 74 79 2E 70 6F 73 74 22 2C 22 69 64 22 3A 22 30 30 30 30 30 30 30 31 22 2C 22 70 61 72 61 6D 73 22 3A 7B 22 52 75 6E 6E 69 6E 67 53 74 61 74 65 22 3A 31 2C 22 48 75 6D 69 64 69 74 79 22 3A 38 35 2C 22 74 65 6D 70 65 72 61 74 75 72 65 22 3A 32 36 2E 36 2C 7D 2C 22 76 65 72 73 69 6F 6E 22 3A 22 31 2E 30 2E 30 22 7D
(4)用網路助手向伺服器傳送報文後,可以看到伺服器有回覆,在阿里雲平臺看到了重新整理的設定資料。
2、釋出確認 報文格式:固定報頭+可變報頭 只有QoS等級為1時才會有釋出確認報文,其構成如下,本次測試中暫時不用。
2.3.4SUBSCRIBE - 訂閱主題
訂閱報文格式:固定報頭+可變報頭+有效載荷+響應
- 固定報頭:報文型別+保留位+剩餘長度 容易得到訂閱報文的固定報頭為:82 ??
- 可變報頭:為客戶端識別符號,識別符號為10的時如圖: 訂閱報文的可變報頭為:00 0A
- 有效載荷: 主題過濾器+服務質量+保留位 主題過濾器主要用於向伺服器說明客戶端要訂閱的主題,這裡使用阿里雲已建立裝置的主題。 ${deviceName}替換為裝置名稱,即主題過濾器內容為: /sys/gnuyAXcWtth/IOT_F1-01/thing/service/property/set
轉為16進位制:2F 73 79 73 2F 67 6E 75 79 41 58 63 57 74 74 68 2F 49 4F 54 5F 46 31 2D 30 31 2F 74 68 69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65 74 Mqtt協議要求主題過濾器要採用 UTF-8編碼,所以得到最終內容為: 00 35 2F 73 79 73 2F 67 6E 75 79 41 58 63 57 74 74 68 2F 49 4F 54 5F 46 31 2D 30 31 2F 74 68 69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65 74 服務質量要求設定為:QoS0 ,因此取值為 00 得到最終的有效載荷為: 00 35 2F 73 79 73 2F 67 6E 75 79 41 58 63 57 74 74 68 2F 49 4F 54 5F 46 31 2D 30 31 2F 74 68 69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65 74 00
- 整合訂閱報文:固定報頭+可變報頭+有效載荷 82 ?? 00 0A 00 35 2F 73 79 73 2F 67 6E 75 79 41 58 63 57 74 74 68 2F 49 4F 54 5F 46 31 2D 30 31 2F 74 68 69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65 74 00 計算得剩餘長度:58(0x3A),最終得到的訂閱報文為: 82 3A 00 0A 00 35 2F 73 79 73 2F 67 6E 75 79 41 58 63 57 74 74 68 2F 49 4F 54 5F 46 31 2D 30 31 2F 74 68 69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65 74 00
最後把得到的報文透過網路除錯助手傳送給伺服器,得到伺服器的返回資訊如下:
2.3.5 SUBACK-訂閱確認(伺服器下發)
訂閱確認報文格式:固定報頭+可變報頭+有效載荷 上述測試中,傳送訂閱報文後伺服器返回 90 03 00 0A 01 根據確認報文格式可知,已經訂閱成功。
- 確認報文格式如下
2.4 兩個IOT裝置之間的通訊測試
提前在該產品下建立好第兩個裝置。
1、自定義裝置的可訂閱和釋出主題
首先看到裝置1釋出主題,但裝置2是接收不到裝置1的訊息的。
2、裝置2訂閱自定義的主題:/gnuyAXcWtth/IOT_F1-02/user/post/info,訂閱成功後可在裝置2的介面看到已訂閱主題列表中多了該topic。
3、裝置之間需要通訊,則需要透過雲平臺對訊息進行轉發,所以需要在雲平臺做流轉配置,根據文件建立流轉解析器,然後啟動執行解析器即可。參考文件: https://help.aliyun.com/document_detail/68677.html
4、裝置1用MQTT.fx連線平臺,而裝置2則用網路除錯助手與平臺建立好連線。用MQTT.fx釋出裝置1的主題”/gnuyAXcWtth/IOT_F1-02/user/post/info”訊息,可以看到裝置2已經收到了裝置1釋出的”/gnuyAXcWtth/IOT_F1-02/user/post/info”訊息。