今天來學習MQTT協議中關於connect部分,connect是很重要的部分,由於它是Client 與MQTT Broker通訊的基礎,而且提供了不少頗有用的特性,不少場景中均可以用到這些特性。git
仍是理論結合着實踐來說吧,不然擔憂小夥伴們看了睡覺。~~~github
前面已經講過了,MQTT是一種基於發佈訂閱的消息傳輸協議,因此MQTT發佈客戶端能夠發佈消息到1個或多個訂閱客戶端。這個模式很像電視或者收音機的廣播,電臺發佈節目,千家萬戶的觀衆接收節目。全部的消息都是發給MQTT Broker,MQTT Broker再將消息轉發給它的訂閱者。在這個過程當中,須要注意如下幾點:bash
如今應該對MQTT Client和Broker有一個比較清楚的認識了。咱們來討論MQTT Connect的格式吧。服務器
MQTT的固定頭部包含了首字節和可變長度。其中首字節的高4位(bit7~bit4)用於表示報文類型,1表示connect。其它標記字節(bit3~bit0)都爲0,以下表。網絡
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
Byte 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
Byte 2... | Remaining | Length |
在Connect報文中,可變頭部包含10個字節,以下表:tcp
Byte | Description | bit7 | bit6 | bit5 | bi4 | bit3 | bit2 | bit1 | bit0 |
---|---|---|---|---|---|---|---|---|---|
Byte 1 | Length MSB (0) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Byte 2 | Length LSB (4) | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
Byte 3 | 'M' | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 1 |
Byte 4 | 'Q' | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 1 |
Byte 5 | 'T' | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 0 |
Byte 6 | 'T' | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 0 |
Byte 7 | Level(4) | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
Byte 8 | Connect Flag | User Name | Password | Will Retain | Will QoS | Will QoS | Will Flag | Clean Session | Reserved |
Byte 9 | Keep Alive MSB | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Byte 10 | Keep Alive MSB | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 |
可變頭部的內容包含Protocol Name, Protocol Level, Connect Flags以及Keep Alive時間。下面分別介紹:工具
Protocol Name: 字節1-6,這部份內容是固定的,其中字節1和字節2表示協議名稱長度,其內容是0x04。字節3-字節6表示協議名稱"MQTT"的UTF-8編碼。學習
Protocol Level: 字節7,表示協議等級,MQTT 3.1.1協議版本的協議等級是4。測試
Connect Flag: 字節8,鏈接標記,每一位都表示一個標記,Bit0是保留標記。從Bit1~Bit7,分別表示Clean Session, Will Flag等內容。這些標記肯定了Payload是否包含對應的信息。例如,若是Bit7和Bit6的值都爲1,那麼表示這次鏈接的Payload中包含User Name和Password。後邊會分別介紹各個標記的做用。ui
Keep Alive: 字節9和字節10,客戶端與服務器心跳間隔,高字節在前,低字節在後。單位是S,默認是60S。關於Keep Alive,須要注意的事項包括:
咱們來測試一下Keep Alive,觀察PINGREQ和PINGRESP數據包。在命令行終端輸入如下命令,會獲得相應結果:
$ mosquitto_sub -t topic001 -k 5 -d
Client mosqsub|16532-SCNWCL012 sending CONNECT
Client mosqsub|16532-SCNWCL012 received CONNACK (0)
Client mosqsub|16532-SCNWCL012 sending SUBSCRIBE (Mid: 1, Topic: topic001, QoS: 0)
Client mosqsub|16532-SCNWCL012 received SUBACK
Subscribed (mid: 1): 0
Client mosqsub|16532-SCNWCL012 sending PINGREQ
Client mosqsub|16532-SCNWCL012 received PINGRESP
Client mosqsub|16532-SCNWCL012 sending PINGREQ
Client mosqsub|16532-SCNWCL012 received PINGRESP
Client mosqsub|16532-SCNWCL012 sending PINGREQ
Client mosqsub|16532-SCNWCL012 received PINGRESP
複製代碼
上面命令中,-t topic001表示訂閱topic爲topic001的主題,使用 -k 5表示設置keep alive時間間隔爲5S,-d表示啓用Debug模式。從輸出結果能夠看到每隔5S,客戶端會發送PINGREQ,並收到從服務器返回的PINGRESP。
由於Connect Flag的內容比較多,因此單獨用一小節來介紹。
第8字節的Bit0。保留位,必須爲0。
第8字節的Bit1,用於表示是否須要清除Session,若是值爲0,表示保留Session,若是爲1,表示清除Session。
Client和Broker能夠存儲一些Session狀態信息,用於消息的可靠傳輸。咱們知道MQTT是經過定義QoS等級來保證消息的可靠傳輸的,因此Session狀態信息中最重要的就是QoS消息的狀態。以Broker爲例,對於QoS爲1或者2的消息,若是Broker沒法成功投遞消息到Client A,那麼消息狀態會保留下來,當下次Client A從新鏈接時,服務器會根據Session狀態,從新投遞以前失敗的消息。
因此,若是客戶端發佈或訂閱某個topic,而且設置了QoS > 0,那麼Clean Session必須設置爲0。
第8字節的Bit2,用於標記是否發送遺願消息。
遺願消息是指當客戶端非正常斷開時,客戶端但願發送一條消息給一個指定的topic,通知對方本身掉線了。遺願消息的一個應用場景是設備掉線提醒,當設備掉線後,訂閱方(一般是後臺服務器)就知道設備已經掉線了。
當Will Flag設置爲1時,表示須要設置遺願消息,那麼Broker會存儲遺願消息而且在客戶端異常掉線以後,發送遺願消息到指定的topic。
若是Will Flag設置爲1,那麼必需要有Will Topic(遺願主題)和Will Message(遺願消息),而且Will QoS和Will Retain也會被讀取。遺願消息也能夠設置QoS的,這樣能夠確保遺願消息的可靠傳遞。
那麼什麼狀況下Broker會發送Will Message呢?當如下任何一種狀況發生時,Broker會發送遺願消息。
須要注意的是,若是客戶端正常關閉鏈接,在關閉鏈接以前發送了DISCONNECT,遺願消息是不會被髮送的。當客戶端發送DISCONNECT請求後,遺願消息會被Broker刪除。
第8字節的Bit3和Bit4,用來設置遺願消息的QoS,由於QoS有3個值,0,1和2,因此用兩位來表示,高位在前,低位在後。
第8字節的Bit5,用於標記是否保留遺願消息。
Will Retain這個標記也頗有用,若是設置成1,當客戶端掉線後,以後全部新的訂閱者訂閱Will Topic時,都能收到遺願消息。QoS > 0只能報紙以前訂閱過的訂閱者收到消息,Will Retain能確保新的訂閱者也接收到消息。
用於標記Payload中是否包含User Name信息。若是設置成1,payload中必須包含User Name信息。
用於標記Payload中是否包含Password信息。若是設置成1,payload中必須包含Password信息。須要注意的是,若是User Name Flag設置成0,Password Flag必須設置成0。可是能夠只包含用戶名,不包含密碼,因此User Name Flag設置成1時,Password Flag也能夠設置成0.
可變頭中第9和第10字節用來表示Keep Alive時間。這個時間是MQTT的心跳時間,單位是秒,默認值是60S。在Client和Broker沒有數據交互的狀況下,Client須要發送PINGREQ給Broker,Broker回覆PINGRESP,用於檢測客戶端是否在線。關於Keep Alive,須要注意一下幾點:
咱們來看一個CONNECT報文的可變頭的例子,加深理解。
描述 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
---|---|---|---|---|---|---|---|---|---|
協 | 議 | 類 | 型 | ||||||
字節1 | 類型長度高位 (0) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
字節2 | 類型長度低位 (4) | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
字節3 | 'M' | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 1 |
字節4 | 'Q' | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 1 |
字節5 | 'T' | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 0 |
字節6 | 'T' | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 0 |
協 | 議 | 等 | 級 | ||||||
字7 | Level (4) | ||||||||
連 | 接 | 標 | 記 | ||||||
字節8 | 見備註 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 0 |
Keep | Alive | ||||||||
字節9 | Keep Alive高位 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
字節10 | Keep Alive低位 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 |
以上字節8,分別表示 User Name Flag (1),Password Flag (1),Will Retain (0),Will QoS (01),Will Flag (1),Clean Session (1),Reserved (0)
介紹完可變頭,咱們來看消息體。
CONNECT協議是包含消息體的,其內容分別是Client Id/[Will Topic]/[Will Message]/[User Name]/[Password]。
以上類型順序不能變化,除了Client Id以外,其它內容都是可選的,只有Connect Flag中對應的值爲1時,其Payload中才包含相關內容。如只有可變頭中CONNECT Flag部分,其Will Flag爲1時,Payload中才會出現Will Topic和Will Message。
對於全部Payload中的內容,前兩位是長度,後面是數據內容。咱們後邊會經過Wire Shark抓包來驗證這一點。
Client Id是客戶端的惟一標識,這個Id不能重複,若是兩個客戶端使用了相同的Id,那麼就會出現互相踢對方的現象,若是你的客戶端一直在斷開-鏈接-斷開這樣的進行循環,就要考慮是不是Client Id重複了。關於Client Id,須要注意如下事項:
當Will Flag設置成1時,Payload中就包含Will Topic和Will Message。Will Topic和Will Message已經講過,這裏再也不贅述。
若是這兩個Connection Flag被設置成1,那麼Payload中就包含User Name和Password。經過User Name和Password能夠對Client進行身份驗證和受權。身份驗證能夠決定是否容許客戶端鏈接,受權能夠限制客戶端容許訪問某些資源(好比topic)。這個屬於Broker中客戶端管理的內容,咱們後邊會介紹。
當客戶端發送一個CONNECT報文後,服務器須要回覆一個CONNACK報文。若是客戶端發送CONNECT給Broker,在一段時間內沒有收到CONNACK,那麼客戶端須要關閉當前鏈接。
CONNACK固定頭以下:
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
Byte 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
Byte 2 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
再次回憶一下固定頭的格式,第一字節高4位表示協議類型,2表示CONNACK,低4位是某些協議的標記位,對於CONNACK,這是保留字段。
固定頭的後邊部分是Remaining Length,CONNACK的協議,其內容長度是2,用一個字節表示。
可變頭包含兩個字節,其格式以下:
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
Byte 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | X |
Byte 2 | X | X | X | X | X | X | X | X |
第一字節Bit7-Bit1是保留位,Bit 0用於表示Session Present。
第二字節是Return Code。咱們分別介紹。
Session Present用來代表服務端是否存在Session狀態。前邊講過,當Clean Session設置爲0時,服務端會保留Session的一些狀態,當客戶端從新鏈接時,那麼服務器會根據Client Id判斷是否存在Session Status,若是存在,該值爲1,不然爲0。關於Session Present,須要注意如下兩點:
當客戶端發起鏈接而且設置Clean Session爲1時,無論服務器是否存在Session Status,Session Present老是爲0.
若是Broker返回的Return Code不爲0,那麼Session Present必須爲0.
CONNACK可變頭的第二字節表示Return Code,代表鏈接的返回狀態。0表示成功,其餘數字表示異常,Return Code列表以下:
值 | 描述 |
---|---|
0X00 | 正常 |
0X01 | 服務器不支持協議當前協議版本 |
0X02 | 拒絕鏈接,Client Id沒有鏈接權限 |
0X03 | 拒絕鏈接,服務端不可用 |
0X04 | 拒絕鏈接,用戶名密碼錯誤 |
0X05 | 拒絕鏈接,沒有受權 |
0X06-0XFF | 保留 |
上邊0X02和0X05的區別,0X02是身份驗證拒絕鏈接,0X05是受權驗證失敗,拒絕鏈接。
CONNACK沒有Payload。
講了這麼多,咱們如今來抓包測試一下,驗證咱們上面講述的內容。
打開Wire Shark,監聽本地環回網卡,過濾條件輸入tcp.port==1883
打開終端,輸入如下命令,發送一條消息。
mosquitto_pub -d -t topic1 --will-qos 2 --will-topic "will_topic" --will-payload "I'm offline!" -u "zengbiaobiao" -P "password" -m "Hello MQTT"
複製代碼
回到Wire Shark,查看CONNECT數據包,以下圖。
當咱們查看CONNECT協議時,能夠看到其詳細數據內容,
第一塊區域,顯示固定頭以及消息長度,Msg Len: 85,以後是協議名稱長度以及協議名稱。
第二塊區域,顯示了CONNECT Flags,對應着咱們發送消息是設置的參數。
第三塊區域,顯示了Payload的內容,每一項內容以前,都有兩字節用於表示內容長度。
今天介紹了MQTT中CONNECT 以及CONNACK協議類型。CONNECT協議類型包含協議頭,可變頭和消息體。重點介紹了CONNECT Flags以及鏈接報文中的一些重要特性,這些特性包括Clean Session, Will Topic, Keep Alive, User/Password等等,這些特性都很適用。
另外咱們也介紹了CONNACK,最後經過一個實驗進行抓包測試,驗證咱們所講的內容。
若是你有