本篇會把鏈接(CONNECT)、心跳(PINGREQ/PINGRESP)、確認(CONNACK)、斷開鏈接(DISCONNECT)和在一塊兒。php
像前面所說,MQTT有關字符串部分採用的修改版的UTF-8編碼,CONNECT可變頭部中協議名稱、消息體都是採用修改版的UTF-8編碼。前面基本上可變頭部內容很少,下面是一個較爲完整的CONNECT消息結構:html
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)java 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個字符,不是必須。 |
||||||||||
Passwordsession 若是設置Password標識,即可讀取用戶密碼。建議密碼爲12個字符或者更少,但不是必須。 |
協議名稱和協議版本都是固定的。ide
一個字節表示,除了第1位是保留未使用,其它7位都具備不一樣含義。編碼
業務上很重要,對消息整體流程影響很大,須要牢記。spa
0,表示若是訂閱的客戶機斷線了,要保存爲其要推送的消息(QoS爲1和QoS爲2),若其從新鏈接時,需將這些消息推送(若客戶端長時間不鏈接,須要設置一個過時值)。 1,斷線服務器即清理相關信息,從新鏈接上來以後,會再次訂閱。
定義了客戶端(沒有主動發送DISCONNECT消息)出現網絡異常致使鏈接中斷的狀況下,服務器須要作的一些措施。
簡而言之,就是客戶端預先定義好,在本身異常斷開的狀況下,所留下的最後遺願(Last Will),也稱之爲遺囑(Testament)。 這個遺囑就是一個由客戶端預先定義好的主題和對應消息,附加在CONNECT的可變頭部中,在客戶端鏈接出現異常的狀況下,由服務器主動發佈此消息。
只有在Will Flag位爲1時,Will Qos和Will Retain纔會被讀取,此時消息體payload中要出現Will Topic和Will Message具體內容,不然,Will QoS和Will Retain值會被忽略掉。
兩位表示,和PUBLISH消息固定頭部的QoS level含義同樣。這裏先掠過,到PUBLISH消息再回過頭來看看,會更明白些。
若標識了Will Flag值爲1,那麼Will QoS就會生效,不然會被忽略掉。
若是設置Will Flag,Will Retain標誌就是有效的,不然它將被忽略。
當客戶端意外斷開服務器發佈其Will Message以後,服務器是否應該繼續保存。這個屬性和PUBLISH固定頭部的RETAIN標誌含義同樣,這裏先掠過。
用於受權,二者要麼爲0要麼爲1,不然都是無效。都爲0,表示客戶端可自由鏈接/訂閱,都爲1,表示鏈接/訂閱須要受權。
消息體定義的消息順序(如上表所示),約定俗成,不得更改,不然將可能引發混亂。
若Will Flag值爲0,那麼在payload中,Client Identifer後面就不會存在Will Topic和Will Message內容。
若User Name和Password都爲0,意味着Payload/消息體中,找不到User Name和password的值,就算有,也是無效。標誌決定着是否讀取與否。
以秒爲單位,定義服務器端從客戶端接收消息的最大時間間隔。通常應用服務會在業務層次檢測客戶端網絡是否鏈接,不是TCP/IP協議層面的心跳機制(好比開啓SOCKET的SO_KEEPALIVE選項)。 通常來說,在一個心跳間隔內,客戶端發送一個PINGREQ消息到服務器,服務器返回PINGRESP消息,完成一次心跳交互,繼而等待下一輪。若客戶端沒有收到心跳反饋,會關閉掉TCP/IP端口鏈接,離線。 16位兩個字節,可看作一個無符號的short類型值。最大值,2^16-1 = 65535秒 = 18小時。最小值能夠爲0,表示客戶端不斷開。通常設爲幾分鐘,好比微信心跳週期爲300秒。
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消息以後,服務器應該返回一個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選項),業務層面定義心跳機制,會讓鏈接狀態檢測、控制更爲直觀。
由客戶端發送到服務器端,證實本身還在一直鏈接着呢。兩個字節,固定值。
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表示。
服務器收到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套接字鏈接。
客戶端主動發送到服務器端,代表即將關閉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」所設置值,再次複習一下:
值爲0,服務器必須在客戶端斷開以後繼續存儲/保持客戶端的訂閱狀態。這些狀態包括:
值爲1,服務器須要馬上清理鏈接狀態數據。
有一點須要牢記,服務器在接收到客戶端發送的DISCONNECT消息以後,須要主動關閉TCP/IP鏈接。
原文 http://www.blogjava.net/yongboy/archive/2014/02/09/409630.html