MQTT 協議 -- CONNECT & CONNACK

概述

今天來學習MQTT協議中關於connect部分,connect是很重要的部分,由於它是Client 與MQTT Broker通訊的基礎,而且提供了不少頗有用的特性,不少場景中均可以用到這些特性。git

仍是理論結合着實踐來說吧,不然擔憂小夥伴們看了睡覺。~~~github

前面已經講過了,MQTT是一種基於發佈訂閱的消息傳輸協議,因此MQTT發佈客戶端能夠發佈消息到1個或多個訂閱客戶端。這個模式很像電視或者收音機的廣播,電臺發佈節目,千家萬戶的觀衆接收節目。全部的消息都是發給MQTT Broker,MQTT Broker再將消息轉發給它的訂閱者。在這個過程當中,須要注意如下幾點:bash

  • 全部客戶端都有一個惟一Id,這個Id只是一個標記,不是客戶地址。發佈端發佈消息時,只能發給某個topic,而不能將消息發給某個地址或Id。
  • 客戶端的Id不能重複。若是有一個客戶端Client A連到MQTT Broker,若是以後又有一個客戶端Client B連到MQTT Broker,此時MQTT Broker會斷開Client A的鏈接。由於MQTT 客戶端有自動鏈接功能,Client A斷開鏈接以後,嘗試從新鏈接到MQTT Broker,此時Client B又會斷開,以後Client B又進行重連,而後兩個Client就會進入斷開-鏈接-斷開的死循環。
  • MQTT Broker負責接收消息,並進行過濾,將消息轉發給訂閱了相關主題的Client。
  • Publisher和Subscriber沒有直接的關聯。他們都只與MQTT Broker進行鏈接。
  • 客戶端既能夠發送消息,也能夠接收消息。
  • 一般狀況下,MQTT Broker是不存儲消息的。

如今應該對MQTT Client和Broker有一個比較清楚的認識了。咱們來討論MQTT Connect的格式吧。服務器


CONNECT

Fixed Header

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
Variable Header

在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,須要注意的事項包括:

  • 當客戶端和服務器之間沒有消息傳輸時,客戶端會每隔60S(keep alive值)向MQTT Broker發送PINGREQ數據報文。服務器須要回覆PINGRESP數據報文。
  • 若是客戶端在發送PINGREQ數據包一段時間後沒有收到PINGRESP數據包,客戶端會斷開鏈接。
  • 若是Keep Alive的值設置爲大於0(假設60S),在沒有數據交互的狀況下,服務器若是在超過1.5倍Keep Alive時間(90S)後沒有收到PINGREQ數據包,則服務器會斷開與當前客戶端的鏈接。
  • Keep Alive能夠設置爲0,那麼客戶端不會發送PINGREQ數據包,服務器也不會由於沒有收到PINGREQ而斷開客戶端鏈接。

咱們來測試一下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

由於Connect Flag的內容比較多,因此單獨用一小節來介紹。

Reserved

第8字節的Bit0。保留位,必須爲0。

Clean Session

第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。

Will Flag

第8字節的Bit2,用於標記是否發送遺願消息。

遺願消息是指當客戶端非正常斷開時,客戶端但願發送一條消息給一個指定的topic,通知對方本身掉線了。遺願消息的一個應用場景是設備掉線提醒,當設備掉線後,訂閱方(一般是後臺服務器)就知道設備已經掉線了。

當Will Flag設置爲1時,表示須要設置遺願消息,那麼Broker會存儲遺願消息而且在客戶端異常掉線以後,發送遺願消息到指定的topic。

若是Will Flag設置爲1,那麼必需要有Will Topic(遺願主題)和Will Message(遺願消息),而且Will QoS和Will Retain也會被讀取。遺願消息也能夠設置QoS的,這樣能夠確保遺願消息的可靠傳遞。

那麼什麼狀況下Broker會發送Will Message呢?當如下任何一種狀況發生時,Broker會發送遺願消息。

  • 網絡異常
  • 服務器在Keep Alive超時後沒有收到PINGREQ
  • 客戶端在關閉鏈接以前,沒有先發送DISCONNECT
  • 服務器由於協議錯誤關閉客戶端鏈接

須要注意的是,若是客戶端正常關閉鏈接,在關閉鏈接以前發送了DISCONNECT,遺願消息是不會被髮送的。當客戶端發送DISCONNECT請求後,遺願消息會被Broker刪除。

Will QoS

第8字節的Bit3和Bit4,用來設置遺願消息的QoS,由於QoS有3個值,0,1和2,因此用兩位來表示,高位在前,低位在後。

Will Retain

第8字節的Bit5,用於標記是否保留遺願消息。

Will Retain這個標記也頗有用,若是設置成1,當客戶端掉線後,以後全部新的訂閱者訂閱Will Topic時,都能收到遺願消息。QoS > 0只能報紙以前訂閱過的訂閱者收到消息,Will Retain能確保新的訂閱者也接收到消息。

User Name Flag

用於標記Payload中是否包含User Name信息。若是設置成1,payload中必須包含User Name信息。

Password Flag

用於標記Payload中是否包含Password信息。若是設置成1,payload中必須包含Password信息。須要注意的是,若是User Name Flag設置成0,Password Flag必須設置成0。可是能夠只包含用戶名,不包含密碼,因此User Name Flag設置成1時,Password Flag也能夠設置成0.

Keep Alive

可變頭中第9和第10字節用來表示Keep Alive時間。這個時間是MQTT的心跳時間,單位是秒,默認值是60S。在Client和Broker沒有數據交互的狀況下,Client須要發送PINGREQ給Broker,Broker回覆PINGRESP,用於檢測客戶端是否在線。關於Keep Alive,須要注意一下幾點:

  • 客戶端負責發送心跳PINGREQ,服務端只管在接收到心跳時回覆PINGRESP。
  • 客戶端在發送PINGREQ一段時間後,未收到回覆,客戶端將關閉鏈接。
  • 服務端在Keep Alive的1.5被時間以後,沒有收到客戶端的任何數據,包括PINGREQ,也會關閉鏈接。
  • Keep Alive能夠設置爲0,表示不啓用心跳機制,那麼客戶端,服務器都不會由於未收到心跳或回覆而關閉鏈接。
示例

咱們來看一個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)

Payload

介紹完可變頭,咱們來看消息體。

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

Client Id是客戶端的惟一標識,這個Id不能重複,若是兩個客戶端使用了相同的Id,那麼就會出現互相踢對方的現象,若是你的客戶端一直在斷開-鏈接-斷開這樣的進行循環,就要考慮是不是Client Id重複了。關於Client Id,須要注意如下事項:

  • Client Id要惟一,而且最好能有必定意義而且可讀,如咱們公司是使用<客戶端類型>_<設備編號>做爲Client Id。如前面提到的,雖然不能直接給Client Id發消息,可是在問題排查時,Client Id仍是有用的。如EMQX的後臺管理工具,能夠直接根據Client Id判斷客戶端是否在線,以及追蹤某個Client Id發送和接收的全部消息。這對於在線排查問題頗有幫助。
  • Client Id能夠爲空,若是爲空,服務器會自動分配一個Id,以後也會一直使用分配的Id,直到網絡斷開。可是生產環境中,不建議這樣使用。
  • Client Id是和Session關聯的,因此若是你的項目中使用到了QoS > 0,那麼不能使用隨機的Client Id。
Will Topic & Will Message

當Will Flag設置成1時,Payload中就包含Will Topic和Will Message。Will Topic和Will Message已經講過,這裏再也不贅述。

User Name & Password

若是這兩個Connection Flag被設置成1,那麼Payload中就包含User Name和Password。經過User Name和Password能夠對Client進行身份驗證和受權。身份驗證能夠決定是否容許客戶端鏈接,受權能夠限制客戶端容許訪問某些資源(好比topic)。這個屬於Broker中客戶端管理的內容,咱們後邊會介紹。


CONNACK

當客戶端發送一個CONNECT報文後,服務器須要回覆一個CONNACK報文。若是客戶端發送CONNECT給Broker,在一段時間內沒有收到CONNACK,那麼客戶端須要關閉當前鏈接。

Fixed Header

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,用一個字節表示。

Variable Header

可變頭包含兩個字節,其格式以下:

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 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.

Return Code

CONNACK可變頭的第二字節表示Return Code,代表鏈接的返回狀態。0表示成功,其餘數字表示異常,Return Code列表以下:

描述
0X00 正常
0X01 服務器不支持協議當前協議版本
0X02 拒絕鏈接,Client Id沒有鏈接權限
0X03 拒絕鏈接,服務端不可用
0X04 拒絕鏈接,用戶名密碼錯誤
0X05 拒絕鏈接,沒有受權
0X06-0XFF 保留

上邊0X02和0X05的區別,0X02是身份驗證拒絕鏈接,0X05是受權驗證失敗,拒絕鏈接。

Payload

CONNACK沒有Payload。


測試

講了這麼多,咱們如今來抓包測試一下,驗證咱們上面講述的內容。

  1. 打開Wire Shark,監聽本地環回網卡,過濾條件輸入tcp.port==1883

  2. 打開終端,輸入如下命令,發送一條消息。

    mosquitto_pub -d -t topic1 --will-qos 2 --will-topic "will_topic" --will-payload "I'm offline!" -u "zengbiaobiao" -P "password" -m "Hello MQTT"
    複製代碼
  3. 回到Wire Shark,查看CONNECT數據包,以下圖。

mqtt-connect

當咱們查看CONNECT協議時,能夠看到其詳細數據內容,

第一塊區域,顯示固定頭以及消息長度,Msg Len: 85,以後是協議名稱長度以及協議名稱。

第二塊區域,顯示了CONNECT Flags,對應着咱們發送消息是設置的參數。

第三塊區域,顯示了Payload的內容,每一項內容以前,都有兩字節用於表示內容長度。


總結

今天介紹了MQTT中CONNECT 以及CONNACK協議類型。CONNECT協議類型包含協議頭,可變頭和消息體。重點介紹了CONNECT Flags以及鏈接報文中的一些重要特性,這些特性包括Clean Session, Will Topic, Keep Alive, User/Password等等,這些特性都很適用。

另外咱們也介紹了CONNACK,最後經過一個實驗進行抓包測試,驗證咱們所講的內容。

若是你有


全部文章在Github上同步,你也能夠訪問個人我的博客點擊查看

相關文章
相關標籤/搜索