MQTT協(xié)議筆記之連接和心跳
前言
本篇會(huì)把連接(CONNECT)、心跳(PINGREQ/PINGRESP)、確認(rèn)(CONNACK)、斷開(kāi)連接(DISCONNECT)和在一起。
CONNECT
像前面所說(shuō),MQTT有關(guān)字符串部分采用的修改版的UTF-8編碼,CONNECT可變頭部中協(xié)議名稱、消息體都是采用修改版的UTF-8編碼。前面基本上可變頭部?jī)?nèi)容不多,下面是一個(gè)較為完整的CONNECT消息結(jié)構(gòu):
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個(gè)字符長(zhǎng)度,客戶端到服務(wù)器的全局唯一標(biāo)志,如果客戶端ID超出23個(gè)字符長(zhǎng)度,服務(wù)器需要返回碼為2,標(biāo)識(shí)符被拒絕響應(yīng)的CONNACK消息。處理QoS級(jí)別1和2的消息ID中,可以使用到。 必填項(xiàng)。 | ||||||||||
Will Topic Will Flag值為1,這里便是Will Topic的內(nèi)容。QoS級(jí)別通過(guò)Will QoS字段定義,RETAIN值通過(guò)Will RETAIN標(biāo)識(shí),都定義在可變頭里面。 | ||||||||||
Will Message Will Flag若設(shè)為1,這里便是Will Message定義消息的內(nèi)容,對(duì)應(yīng)的主題為Will Topic。如果客戶端意外的斷開(kāi)觸發(fā)服務(wù)器PUBLISH此消息。長(zhǎng)度有可能為0。 在CONNECT消息中的Will Message是UTF-8編碼的,當(dāng)被服務(wù)器發(fā)布時(shí)則作為二進(jìn)制的消息體。 | ||||||||||
User Name 如果設(shè)置User Name標(biāo)識(shí),可以在此讀取用戶名稱。一般可用于身份驗(yàn)證。協(xié)議建議用戶名為不多于12個(gè)字符,不是必須。 | ||||||||||
Password 如果設(shè)置Password標(biāo)識(shí),便可讀取用戶密碼。建議密碼為12個(gè)字符或者更少,但不是必須。 |
可變頭部
協(xié)議名稱和協(xié)議版本都是固定的。
連接標(biāo)志(Connect Flags)
一個(gè)字節(jié)表示,除了第1位是保留未使用,其它7位都具有不同含義。
業(yè)務(wù)上很重要,對(duì)消息總體流程影響很大,需要牢記。
Clean Session
0,表示如果訂閱的客戶機(jī)斷線了,要保存為其要推送的消息(QoS為1和QoS為2),若其重新連接時(shí),需將這些消息推送(若客戶端長(zhǎng)時(shí)間不連接,需要設(shè)置一個(gè)過(guò)期值)。 1,斷線服務(wù)器即清理相關(guān)信息,重新連接上來(lái)之后,會(huì)再次訂閱。
Will Flag
定義了客戶端(沒(méi)有主動(dòng)發(fā)送DISCONNECT消息)出現(xiàn)網(wǎng)絡(luò)異常導(dǎo)致連接中斷的情況下,服務(wù)器需要做的一些措施。
簡(jiǎn)而言之,就是客戶端預(yù)先定義好,在自己異常斷開(kāi)的情況下,所留下的最后遺愿(Last Will),也稱之為遺囑(Testament)。 這個(gè)遺囑就是一個(gè)由客戶端預(yù)先定義好的主題和對(duì)應(yīng)消息,附加在CONNECT的可變頭部中,在客戶端連接出現(xiàn)異常的情況下,由服務(wù)器主動(dòng)發(fā)布此消息。
只有在Will Flag位為1時(shí),Will Qos和Will Retain才會(huì)被讀取,此時(shí)消息體payload中要出現(xiàn)Will Topic和Will Message具體內(nèi)容,否則,Will QoS和Will Retain值會(huì)被忽略掉。
Will Qos
兩位表示,和PUBLISH消息固定頭部的QoS level含義一樣。這里先掠過(guò),到PUBLISH消息再回過(guò)頭來(lái)看看,會(huì)更明白些。
若標(biāo)識(shí)了Will Flag值為1,那么Will QoS就會(huì)生效,否則會(huì)被忽略掉。
Will RETAIN
如果設(shè)置Will Flag,Will Retain標(biāo)志就是有效的,否則它將被忽略。
當(dāng)客戶端意外斷開(kāi)服務(wù)器發(fā)布其Will Message之后,服務(wù)器是否應(yīng)該繼續(xù)保存。這個(gè)屬性和PUBLISH固定頭部的RETAIN標(biāo)志含義一樣,這里先掠過(guò)。
User name 和 password Flag:
用于授權(quán),兩者要么為0要么為1,否則都是無(wú)效。都為0,表示客戶端可自由連接/訂閱,都為1,表示連接/訂閱需要授權(quán)。
Payload/消息體
消息體定義的消息順序(如上表所示),約定俗成,不得更改,否則將可能引起混亂。
若Will Flag值為0,那么在payload中,Client Identifer后面就不會(huì)存在Will Topic和Will Message內(nèi)容。
若User Name和Password都為0,意味著Payload/消息體中,找不到User Name和password的值,就算有,也是無(wú)效。標(biāo)志決定著是否讀取與否。
心跳時(shí)間(Keep Alive timer)
以秒為單位,定義服務(wù)器端從客戶端接收消息的最大時(shí)間間隔。一般應(yīng)用服務(wù)會(huì)在業(yè)務(wù)層次檢測(cè)客戶端網(wǎng)絡(luò)是否連接,不是TCP/IP協(xié)議層面的心跳機(jī)制(比如開(kāi)啟SOCKET的SO_KEEPALIVE選項(xiàng))。 一般來(lái)講,在一個(gè)心跳間隔內(nèi),客戶端發(fā)送一個(gè)PINGREQ消息到服務(wù)器,服務(wù)器返回PINGRESP消息,完成一次心跳交互,繼而等待下一輪。若客戶端沒(méi)有收到心跳反饋,會(huì)關(guān)閉掉TCP/IP端口連接,離線。 16位兩個(gè)字節(jié),可看做一個(gè)無(wú)符號(hào)的short類型值。最大值,2^16-1 = 65535秒 = 18小時(shí)。最小值可以為0,表示客戶端不斷開(kāi)。一般設(shè)為幾分鐘,比如微信心跳周期為300秒。
Will Message編碼
Will Message在CONNECT Payload/息體中,使用UTF-8編碼。假設(shè)內(nèi)容為“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 |
有一點(diǎn)需要記住,PUBLISH的Payload/消息體中以二進(jìn)制編碼保存。
某刻客戶端異常關(guān)閉觸發(fā)服務(wù)器會(huì)PUBLISH此消息。那么服務(wù)器會(huì)直接把byte3-byte6之間字符取出,保存為二進(jìn)制,附加到PUBLISH消息體中,大概存儲(chǔ)如下:
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協(xié)議對(duì)Will message的說(shuō)明很容易引起誤解,3.1.1草案已經(jīng)得到修正。
相關(guān)說(shuō)明:
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)異常中斷,便會(huì)觸發(fā)服務(wù)器發(fā)布Will Message消息到Will Topic主題上去,通知Will Topic訂閱者,對(duì)方因異常退出。
接收CONNECT后的響應(yīng)動(dòng)作
接收到CONNECT消息之后,服務(wù)器應(yīng)該返回一個(gè)CONNACK消息作為響應(yīng):
- 若客戶端繞過(guò)CONNECT消息直接發(fā)送其它類型消息,服務(wù)器應(yīng)關(guān)閉此非法連接 若客戶端發(fā)送CONNECT之后未收到CONNACT,需要關(guān)閉當(dāng)前連接,然后重新連接
- 相同Client ID客戶端已連接到服務(wù)器,先前客戶端必須斷開(kāi)連接后,服務(wù)器才能完成新的客戶端CONNECT連接 客戶端發(fā)送無(wú)效非法CONNECT消息,服務(wù)器需要關(guān)閉
CONNACK
一個(gè)完整的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 |
可變頭部第一個(gè)字節(jié)為保留,無(wú)甚用處。第二個(gè)字節(jié)為連接握手返回碼:
返回值 | 16進(jìn)制 | 含義 |
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,表示連接建立。非法的請(qǐng)求,需要返回相應(yīng)的數(shù)值。
從上面看出,一個(gè)CONNACT,四個(gè)字節(jié)表示。一個(gè)正常的CONNACT消息實(shí)際內(nèi)容可能如下: 0x20 0x02 0x00 0x00
若是在私有協(xié)議中,兩個(gè)字節(jié)就足夠了。
很多時(shí)候,客戶端和服務(wù)器端在沒(méi)有消息傳遞時(shí),會(huì)一直保持著連接。雖然不能依靠TCP心跳機(jī)制(比如SO_KEEPALIVE選項(xiàng)),業(yè)務(wù)層面定義心跳機(jī)制,會(huì)讓連接狀態(tài)檢測(cè)、控制更為直觀。
PINGREQ
由客戶端發(fā)送到服務(wù)器端,證明自己還在一直連接著呢。兩個(gè)字節(jié),固定值。
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 |
客戶端會(huì)在一個(gè)心跳周期內(nèi)發(fā)送一條PINGREQ消息到服務(wù)器端。
心跳頻率在CONNECT可變頭部“Keep Alive timer”中定義時(shí)間,單位為秒,無(wú)符號(hào)16位short表示。
PINGRESP
服務(wù)器收到PINGREQ請(qǐng)求之后,會(huì)立即響應(yīng)一個(gè)兩個(gè)字節(jié)固定格式的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 |
服務(wù)器一般若在1.5倍的心跳周期內(nèi)接收不到客戶端發(fā)送的PINGREQ,可考慮關(guān)閉客戶端的連接描述符。此時(shí)的關(guān)閉連接的行為和接收到客戶端發(fā)送DISCONNECT消息的處理行為一致,但對(duì)客戶端的訂閱不會(huì)產(chǎn)生影響(不會(huì)清除客戶端訂閱數(shù)據(jù)),這個(gè)需要牢記。
若客戶端發(fā)送PINGREQ之后的一個(gè)心跳周期內(nèi)接收不到PINGRESP消息,可考慮關(guān)閉TCP/IP套接字連接。
DISCONNECT
客戶端主動(dòng)發(fā)送到服務(wù)器端,表明即將關(guān)閉TCP/IP連接。此時(shí)要求服務(wù)器要完整、干凈的進(jìn)行斷開(kāi)處理,不能僅僅類似于關(guān)閉連接描述符類似草草處理之。 需要兩個(gè)字節(jié),值固定:
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 |
服務(wù)器要根據(jù)先前此客戶端在發(fā)送CONNECT消息可變頭部Connect flag中的“Clean session flag”所設(shè)置值,再次復(fù)習(xí)一下:
值為0,服務(wù)器必須在客戶端斷開(kāi)之后繼續(xù)存儲(chǔ)/保持客戶端的訂閱狀態(tài)。這些狀態(tài)包括:
- 存儲(chǔ)訂閱的消息QoS1和QoS2消息
- 正在發(fā)送消息期間連接丟失導(dǎo)致發(fā)送失敗的消息
- 以便當(dāng)客戶端重新連接時(shí)以上消息可以被重新傳遞。
值為1,服務(wù)器需要立刻清理連接狀態(tài)數(shù)據(jù)。
有一點(diǎn)需要牢記,服務(wù)器在接收到客戶端發(fā)送的DISCONNECT消息之后,需要主動(dòng)關(guān)閉TCP/IP連接。
posted on 2014-02-09 13:41 nieyong 閱讀(60259) 評(píng)論(7) 編輯 收藏 所屬分類: MQTT