MQTT協議筆記之連接和心跳


前言

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

CONNECT

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

  Description 7 6 5 4 3 2 1 0
Fixed header/固定頭部
    Message Type(1) DUP flag QoS level RETAIN
byte 1
  0 0 0 1 x x x x
byte 2 Remaining Length
Variable header/可變頭部
Protocol Name
byte 1 Length MSB (0) 0 0 0 0 0 0 0 0
byte 2 Length LSB (6) 0 0 0 0 0 1 1 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 'I' 0 1 0 0 1 0 0 1
byte 6 's' 0 1 1 1 0 0 1 1
byte 7 'd' 0 1 1 0 0 1 0 0
byte 8 'p' 0 1 1 1 0 0 0 0
Protocol Version Number
byte 9 Version (3) 0 0 0 0 0 0 1 1
Connect Flags
  User Name Flag Password Flag Will Retain Will QoS Will Flag Clean Session Reserved
byte 10
1 1 0 0 1 1 1 x
Keep Alive timer
byte 11 Keep Alive MSB (0) 0 0 0 0 0 0 0 0
byte 12 Keep Alive LSB (10) 0 0 0 0 1 0 1 0
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”,大概如下:

  Description 7 6 5 4 3 2 1 0
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 'a' (0x61) 0 1 1 0 0 0 0 1
byte 4 'b' (0x62) 0 1 1 0 0 0 1 0
byte 5 'c' (0x63) 0 1 1 0 0 0 1 1
byte 6 'd' (0x64) 0 1 1 0 0 1 0 0

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

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

  Description 7 6 5 4 3 2 1 0
byte 1 'a' (0x61) 0 1 1 0 0 0 0 1
byte 2 'b' (0x62) 0 1 1 0 0 0 1 0
byte 3 'c' (0x63) 0 1 1 0 0 0 1 1
byte 4 'd' (0x64) 0 1 1 0 0 1 0 0

另外,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消息大致如下:

  Description 7 6 5 4 3 2 1 0
Fixed header/固定頭部
byte 1   Message type (2) DUP flag QoS flags RETAIN
    0 0 1 0 x x x x
byte 2   Remaining Length (2)
    0 0 0 0 0 0 1 0
Variable header/可變頭部
Topic Name Compression Response
byte 1 Reserved values. Not used. x x x x x x x x
Connect Return Code
byte 2 Return Code                

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

返回值 16進制 含義
0 0x00 Connection Accepted
1 0x01 Connection Refused: unacceptable protocol version
2 0x02 Connection Refused: identifier rejected
3 0x03 Connection Refused: server unavailable
4 0x04 Connection Refused: bad user name or password
5 0x05 Connection Refused: not authorized
6-255   Reserved for future use

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

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

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

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

PINGREQ

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

  Description 7 6 5 4 3 2 1 0
Fixed header/固定頭部
byte 1   Message type (12) DUP flag QoS flags RETAIN
    1 1 0 0 x x x x
byte 2   Remaining Length (0)
    0 0 0 0 0 0 0 0

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

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

PINGRESP

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

  Description 7 6 5 4 3 2 1 0
Fixed header/固定頭部
byte 1   Message type (13) DUP flag QoS flags RETAIN
    1 1 0 1 x x x x
byte 2   Remaining Length (0)
    0 0 0 0 0 0 0 0

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

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

DISCONNECT

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

  Description 7 6 5 4 3 2 1 0
Fixed header/固定頭部
byte 1   Message type (14) DUP flag QoS flags RETAIN
    1 1 1 0 x x x x
byte 2   Remaining Length (0)
    0 0 0 0 0 0 0 0

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

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

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

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

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM