聶永的博客

          記錄工作/學習的點點滴滴。

          MQTT協議筆記之連接和心跳

          前言

          本篇會把連接(CONNECT)、心跳(PINGREQ/PINGRESP)、確認(CONNACK)、斷開連接(DISCONNECT)和在一起。

          CONNECT

          像前面所說,MQTT有關字符串部分采用的修改版的UTF-8編碼,CONNECT可變頭部中協議名稱、消息體都是采用修改版的UTF-8編碼。前面基本上可變頭部內容不多,下面是一個較為完整的CONNECT消息結構:

           Description76543210
          Fixed header/固定頭部

           Message Type(1)DUP flagQoS levelRETAIN
          byte 1
           0001xxxx
          byte 2Remaining Length
          Variable header/可變頭部
          Protocol Name
          byte 1Length MSB (0)00000000
          byte 2Length LSB (6)00000110
          byte 3'M'01001101
          byte 4'Q'01010001
          byte 5'I'01001001
          byte 6's'01110011
          byte 7'd'01100100
          byte 8'p'01110000
          Protocol Version Number
          byte 9Version (3)00000011
          Connect Flags

          User Name FlagPassword FlagWill RetainWill QoSWill FlagClean SessionReserved
          byte 10
          1100111x
          Keep Alive timer
          byte 11Keep Alive MSB (0)00000000
          byte 12Keep Alive LSB (10)00001010
          Payload/消息體

          Client Identifier(客戶端ID)

          1-23個字符長度,客戶端到服務器的全局唯一標志,如果客戶端ID超出23個字符長度,服務器需要返回碼為2,標識符被拒絕響應的CONNACK消息。
          處理QoS級別1和2的消息ID中,可以使用到。
          必填項。

          Will Topic

          Will Flag值為1,這里便是Will Topic的內容。QoS級別通過Will QoS字段定義,RETAIN值通過Will RETAIN標識,都定義在可變頭里面。

          Will Message

          Will Flag若設為1,這里便是Will Message定義消息的內容,對應的主題為Will Topic。如果客戶端意外的斷開觸發服務器PUBLISH此消息。
          長度有可能為0。
          在CONNECT消息中的Will Message是UTF-8編碼的,當被服務器發布時則作為二進制的消息體。

          User Name

          如果設置User Name標識,可以在此讀取用戶名稱。一般可用于身份驗證。協議建議用戶名為不多于12個字符,不是必須。

          Password

          如果設置Password標識,便可讀取用戶密碼。建議密碼為12個字符或者更少,但不是必須。

          可變頭部

          協議名稱和協議版本都是固定的。

          連接標志(Connect Flags)

          一個字節表示,除了第1位是保留未使用,其它7位都具有不同含義。

          業務上很重要,對消息總體流程影響很大,需要牢記。

          Clean Session

          0,表示如果訂閱的客戶機斷線了,要保存為其要推送的消息(QoS為1和QoS為2),若其重新連接時,需將這些消息推送(若客戶端長時間不連接,需要設置一個過期值)。 1,斷線服務器即清理相關信息,重新連接上來之后,會再次訂閱。

          Will Flag

          定義了客戶端(沒有主動發送DISCONNECT消息)出現網絡異常導致連接中斷的情況下,服務器需要做的一些措施。

          簡而言之,就是客戶端預先定義好,在自己異常斷開的情況下,所留下的最后遺愿(Last Will),也稱之為遺囑(Testament)。 這個遺囑就是一個由客戶端預先定義好的主題和對應消息,附加在CONNECT的可變頭部中,在客戶端連接出現異常的情況下,由服務器主動發布此消息。

          只有在Will Flag位為1時,Will Qos和Will Retain才會被讀取,此時消息體payload中要出現Will Topic和Will Message具體內容,否則,Will QoS和Will Retain值會被忽略掉。

          Will Qos

          兩位表示,和PUBLISH消息固定頭部的QoS level含義一樣。這里先掠過,到PUBLISH消息再回過頭來看看,會更明白些。

          若標識了Will Flag值為1,那么Will QoS就會生效,否則會被忽略掉。

          Will RETAIN

          如果設置Will Flag,Will Retain標志就是有效的,否則它將被忽略。

          當客戶端意外斷開服務器發布其Will Message之后,服務器是否應該繼續保存。這個屬性和PUBLISH固定頭部的RETAIN標志含義一樣,這里先掠過。

          User name 和 password Flag:

          用于授權,兩者要么為0要么為1,否則都是無效。都為0,表示客戶端可自由連接/訂閱,都為1,表示連接/訂閱需要授權。

          Payload/消息體

          消息體定義的消息順序(如上表所示),約定俗成,不得更改,否則將可能引起混亂。

          若Will Flag值為0,那么在payload中,Client Identifer后面就不會存在Will Topic和Will Message內容。

          若User Name和Password都為0,意味著Payload/消息體中,找不到User Name和password的值,就算有,也是無效。標志決定著是否讀取與否。

          心跳時間(Keep Alive timer)

          以秒為單位,定義服務器端從客戶端接收消息的最大時間間隔。一般應用服務會在業務層次檢測客戶端網絡是否連接,不是TCP/IP協議層面的心跳機制(比如開啟SOCKET的SO_KEEPALIVE選項)。 一般來講,在一個心跳間隔內,客戶端發送一個PINGREQ消息到服務器,服務器返回PINGRESP消息,完成一次心跳交互,繼而等待下一輪。若客戶端沒有收到心跳反饋,會關閉掉TCP/IP端口連接,離線。 16位兩個字節,可看做一個無符號的short類型值。最大值,2^16-1 = 65535秒 = 18小時。最小值可以為0,表示客戶端不斷開。一般設為幾分鐘,比如微信心跳周期為300秒。

          Will Message編碼

          Will Message在CONNECT Payload/息體中,使用UTF-8編碼。假設內容為“abcd”,大概如下:

           Description76543210
          byte 1Length MSB (0)00000000
          byte 2Length LSB (4)00000100
          byte 3'a' (0x61)01100001
          byte 4'b' (0x62)01100010
          byte 5'c' (0x63)01100011
          byte 6'd' (0x64)01100100

          有一點需要記住,PUBLISH的Payload/消息體中以二進制編碼保存。

          某刻客戶端異常關閉觸發服務器會PUBLISH此消息。那么服務器會直接把byte3-byte6之間字符取出,保存為二進制,附加到PUBLISH消息體中,大概存儲如下:

           Description76543210
          byte 1'a' (0x61)01100001
          byte 2'b' (0x62)01100010
          byte 3'c' (0x63)01100011
          byte 4'd' (0x64)01100100

          另外,MQTT 3.1協議對Will message的說明很容易引起誤解,3.1.1草案已經得到修正。

          相關說明:

          http://mqtt.org/wiki/doku.php/willmessageutf8_support

          https://tools.oasis-open.org/issues/browse/MQTT-2

          連接異常中斷通知機制

          CONNECT消息一旦設置在可變頭部設置了Will flag標記,那就啟用了Last-Will-And-Testament特性,此特性很贊。

          一旦客戶端出現異常中斷,便會觸發服務器發布Will Message消息到Will Topic主題上去,通知Will Topic訂閱者,對方因異常退出。

          接收CONNECT后的響應動作

          接收到CONNECT消息之后,服務器應該返回一個CONNACK消息作為響應:

          1. 若客戶端繞過CONNECT消息直接發送其它類型消息,服務器應關閉此非法連接 若客戶端發送CONNECT之后未收到CONNACT,需要關閉當前連接,然后重新連接
          2. 相同Client ID客戶端已連接到服務器,先前客戶端必須斷開連接后,服務器才能完成新的客戶端CONNECT連接 客戶端發送無效非法CONNECT消息,服務器需要關閉

          CONNACK

          一個完整的CONNACK消息大致如下:

           Description76543210
          Fixed header/固定頭部
          byte 1 Message type (2)DUP flagQoS flagsRETAIN
            0010xxxx
          byte 2 Remaining Length (2)
            00000010
          Variable header/可變頭部
          Topic Name Compression Response
          byte 1Reserved values. Not used.xxxxxxxx
          Connect Return Code
          byte 2Return Code        

          可變頭部第一個字節為保留,無甚用處。第二個字節為連接握手返回碼:

          返回值16進制含義
          00x00Connection Accepted
          10x01Connection Refused: unacceptable protocol version
          20x02Connection Refused: identifier rejected
          30x03Connection Refused: server unavailable
          40x04Connection Refused: bad user name or password
          50x05Connection Refused: not authorized
          6-255 Reserved for future use

          只有0-5目前被使用到,其他值有待日后使用。一般返回值為0x00,表示連接建立。非法的請求,需要返回相應的數值。

          從上面看出,一個CONNACT,四個字節表示。一個正常的CONNACT消息實際內容可能如下: 0x20 0x02 0x00 0x00

          若是在私有協議中,兩個字節就足夠了。

          很多時候,客戶端和服務器端在沒有消息傳遞時,會一直保持著連接。雖然不能依靠TCP心跳機制(比如SO_KEEPALIVE選項),業務層面定義心跳機制,會讓連接狀態檢測、控制更為直觀。

          PINGREQ

          由客戶端發送到服務器端,證明自己還在一直連接著呢。兩個字節,固定值。

           Description76543210
          Fixed header/固定頭部
          byte 1 Message type (12)DUP flagQoS flagsRETAIN
            1100xxxx
          byte 2 Remaining Length (0)
            00000000

          客戶端會在一個心跳周期內發送一條PINGREQ消息到服務器端。

          心跳頻率在CONNECT可變頭部“Keep Alive timer”中定義時間,單位為秒,無符號16位short表示。

          PINGRESP

          服務器收到PINGREQ請求之后,會立即響應一個兩個字節固定格式的PINGRESP消息。

           Description76543210
          Fixed header/固定頭部
          byte 1 Message type (13)DUP flagQoS flagsRETAIN
            1101xxxx
          byte 2 Remaining Length (0)
            00000000

          服務器一般若在1.5倍的心跳周期內接收不到客戶端發送的PINGREQ,可考慮關閉客戶端的連接描述符。此時的關閉連接的行為和接收到客戶端發送DISCONNECT消息的處理行為一致,但對客戶端的訂閱不會產生影響(不會清除客戶端訂閱數據),這個需要牢記。

          若客戶端發送PINGREQ之后的一個心跳周期內接收不到PINGRESP消息,可考慮關閉TCP/IP套接字連接。

          DISCONNECT

          客戶端主動發送到服務器端,表明即將關閉TCP/IP連接。此時要求服務器要完整、干凈的進行斷開處理,不能僅僅類似于關閉連接描述符類似草草處理之。 需要兩個字節,值固定:

           Description76543210
          Fixed header/固定頭部
          byte 1 Message type (14)DUP flagQoS flagsRETAIN
            1110xxxx
          byte 2 Remaining Length (0)
            00000000

          服務器要根據先前此客戶端在發送CONNECT消息可變頭部Connect flag中的“Clean session flag”所設置值,再次復習一下:

          1. 值為0,服務器必須在客戶端斷開之后繼續存儲/保持客戶端的訂閱狀態。這些狀態包括:

            • 存儲訂閱的消息QoS1和QoS2消息
            • 正在發送消息期間連接丟失導致發送失敗的消息
            • 以便當客戶端重新連接時以上消息可以被重新傳遞。
          2. 值為1,服務器需要立刻清理連接狀態數據。

          有一點需要牢記,服務器在接收到客戶端發送的DISCONNECT消息之后,需要主動關閉TCP/IP連接。

          posted on 2014-02-09 13:41 nieyong 閱讀(60222) 評論(7)  編輯  收藏 所屬分類: MQTT

          評論

          # re: MQTT協議筆記之連接和心跳 2014-02-10 15:08 電驢資源

          MARK,學習下  回復  更多評論   

          # re: MQTT協議筆記之連接和心跳 2014-10-22 13:49 來雨

          學習了,有系統學習的文章或者書么  回復  更多評論   

          # re: MQTT協議筆記之連接和心跳 2015-03-07 23:20 zer0

          最近在實現MQTT協議的3.1.1版本,遇到一個問題,想請教一下。
          問題是,3.1.1版本的協議P29頁說如果服務器拒絕ClientID,它返回的CONNACK包的return code必須是0x02。
          但是我找了半天,都沒找到協議里說 哪里應該拒絕clientID。

          我想問的是,什么樣的情況下服務器需要拒絕clientID。
            回復  更多評論   

          # re: MQTT協議筆記之連接和心跳 2015-03-09 09:43 nieyong

          @zer0
          clientID可以用于授權,若授權不通過(比如檢測是否他人沒有按照已定義規則生成的的惡意ClientId),可以直接拒絕之。比如在企業/公司內部,用于PUBLISH的MQTT服務器端,可以選擇對ClientId進行指定,若非指定值,則屬于惡意的ClientId。  回復  更多評論   

          # re: MQTT協議筆記之連接和心跳 2015-04-23 15:50 h2appy

          不是playload,而是payload  回復  更多評論   

          # re: MQTT協議筆記之連接和心跳 2015-04-23 18:01 nieyong

          @h2appy
          十分感謝,已做修改。希望您以后給我多提改進意見 :))  回復  更多評論   

          # re: MQTT協議筆記之連接和心跳 2016-01-06 02:23 阿旭

          @nieyong
          clientId 是否可以理解為用戶id這種東西, 我想把用戶id放這兒標識這個客戶端是否可行

          username, password.
          我用netty+mqtt只是實現了推送的功能, 登錄會以http請求另外的服務器. 這時候我就用token代替用戶名密碼作為權限檢查, 我token可以放在username這個字段嗎?

          請指教  回復  更多評論   

          公告

          所有文章皆為原創,若轉載請標明出處,謝謝~

          新浪微博,歡迎關注:

          導航

          <2015年3月>
          22232425262728
          1234567
          891011121314
          15161718192021
          22232425262728
          2930311234

          統計

          常用鏈接

          留言簿(58)

          隨筆分類(130)

          隨筆檔案(151)

          個人收藏

          最新隨筆

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 汕尾市| 大埔区| 涡阳县| 合作市| 都江堰市| 益阳市| 阿鲁科尔沁旗| 淮安市| 会理县| 凤城市| 武城县| 江北区| 武威市| 双城市| 上杭县| 咸丰县| 鹤峰县| 长葛市| 东莞市| 临沭县| 邵武市| 长春市| 论坛| 正宁县| 资阳市| 分宜县| 永和县| 得荣县| 白山市| 麻阳| 济源市| 桐乡市| 平果县| 漳州市| 玛多县| 文水县| 寿宁县| 汨罗市| 洛隆县| 抚远县| 丹阳市|