聶永的博客

          記錄工作/學(xué)習(xí)的點(diǎn)點(diǎn)滴滴。

          MQTT協(xié)議筆記之連接和心跳

          前言

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

          CONNECT

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

           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個字符長度,客戶端到服務(wù)器的全局唯一標(biāo)志,如果客戶端ID超出23個字符長度,服務(wù)器需要返回碼為2,標(biāo)識符被拒絕響應(yīng)的CONNACK消息。
          處理QoS級別1和2的消息ID中,可以使用到。
          必填項(xiàng)。

          Will Topic

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

          Will Message

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

          User Name

          如果設(shè)置User Name標(biāo)識,可以在此讀取用戶名稱。一般可用于身份驗(yàn)證。協(xié)議建議用戶名為不多于12個字符,不是必須。

          Password

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

          可變頭部

          協(xié)議名稱和協(xié)議版本都是固定的。

          連接標(biāo)志(Connect Flags)

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

          業(yè)務(wù)上很重要,對消息總體流程影響很大,需要牢記。

          Clean Session

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

          Will Flag

          定義了客戶端(沒有主動發(fā)送DISCONNECT消息)出現(xiàn)網(wǎng)絡(luò)異常導(dǎo)致連接中斷的情況下,服務(wù)器需要做的一些措施。

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

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

          Will Qos

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

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

          Will RETAIN

          如果設(shè)置Will Flag,Will Retain標(biāo)志就是有效的,否則它將被忽略。

          當(dāng)客戶端意外斷開服務(wù)器發(fā)布其Will Message之后,服務(wù)器是否應(yīng)該繼續(xù)保存。這個屬性和PUBLISH固定頭部的RETAIN標(biāo)志含義一樣,這里先掠過。

          User name 和 password Flag:

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

          Payload/消息體

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

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

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

          心跳時間(Keep Alive timer)

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

          Will Message編碼

          Will Message在CONNECT Payload/息體中,使用UTF-8編碼。假設(shè)內(nèi)容為“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

          有一點(diǎn)需要記住,PUBLISH的Payload/消息體中以二進(jìn)制編碼保存。

          某刻客戶端異常關(guān)閉觸發(fā)服務(wù)器會PUBLISH此消息。那么服務(wù)器會直接把byte3-byte6之間字符取出,保存為二進(jìn)制,附加到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協(xié)議對Will message的說明很容易引起誤解,3.1.1草案已經(jīng)得到修正。

          相關(guān)說明:

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

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

          連接異常中斷通知機(jī)制

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

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

          接收CONNECT后的響應(yīng)動作

          接收到CONNECT消息之后,服務(wù)器應(yīng)該返回一個CONNACK消息作為響應(yīng):

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

          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        

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

          返回值16進(jìn)制含義
          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,表示連接建立。非法的請求,需要返回相應(yīng)的數(shù)值。

          從上面看出,一個CONNACT,四個字節(jié)表示。一個正常的CONNACT消息實(shí)際內(nèi)容可能如下: 0x20 0x02 0x00 0x00

          若是在私有協(xié)議中,兩個字節(jié)就足夠了。

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

          PINGREQ

          由客戶端發(fā)送到服務(wù)器端,證明自己還在一直連接著呢。兩個字節(jié),固定值。

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

          客戶端會在一個心跳周期內(nèi)發(fā)送一條PINGREQ消息到服務(wù)器端。

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

          PINGRESP

          服務(wù)器收到PINGREQ請求之后,會立即響應(yīng)一個兩個字節(jié)固定格式的PINGRESP消息。

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

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

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

          DISCONNECT

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

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

          服務(wù)器要根據(jù)先前此客戶端在發(fā)送CONNECT消息可變頭部Connect flag中的“Clean session flag”所設(shè)置值,再次復(fù)習(xí)一下:

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

            • 存儲訂閱的消息QoS1和QoS2消息
            • 正在發(fā)送消息期間連接丟失導(dǎo)致發(fā)送失敗的消息
            • 以便當(dāng)客戶端重新連接時以上消息可以被重新傳遞。
          2. 值為1,服務(wù)器需要立刻清理連接狀態(tài)數(shù)據(jù)。

          有一點(diǎn)需要牢記,服務(wù)器在接收到客戶端發(fā)送的DISCONNECT消息之后,需要主動關(guān)閉TCP/IP連接。

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

          評論

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

          MARK,學(xué)習(xí)下  回復(fù)  更多評論   

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

          學(xué)習(xí)了,有系統(tǒng)學(xué)習(xí)的文章或者書么  回復(fù)  更多評論   

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

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

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

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

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

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

          不是playload,而是payload  回復(fù)  更多評論   

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

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

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

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

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

          請指教  回復(fù)  更多評論   

          公告

          所有文章皆為原創(chuàng),若轉(zhuǎn)載請標(biāo)明出處,謝謝~

          新浪微博,歡迎關(guān)注:

          導(dǎo)航

          <2015年4月>
          2930311234
          567891011
          12131415161718
          19202122232425
          262728293012
          3456789

          統(tǒng)計(jì)

          常用鏈接

          留言簿(58)

          隨筆分類(130)

          隨筆檔案(151)

          個人收藏

          最新隨筆

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 河津市| 襄城县| 宽城| 华池县| 马龙县| 平舆县| 南开区| 孝感市| 康乐县| 读书| 昌邑市| 双流县| 贺州市| 高州市| 东兰县| 焦作市| 清新县| 嵊州市| 商水县| 大丰市| 弋阳县| 抚宁县| 马龙县| 鞍山市| 尉犁县| 泸溪县| 甘孜| 新丰县| 玛曲县| 东安县| 保定市| 武平县| 洪湖市| 南岸区| 集安市| 贵港市| 高雄市| 万荣县| 太谷县| 辉南县| 根河市|