Mosquitto搭建Android推送服務(一)MQTT簡介


總體概要:

MQTT系列文章分為4部分

1、MQTT簡介

2、mosquitto服務器搭建

3、編寫Mosquitto的可視化工具

4、使用Mosquitto完成Android推送服務

文章鋼要:

對MQTT協議有一定認識

對MQTT運行原理有一定了解

 

一、什么是MQTT

如果使用Mosquitto做Android推送那么一定無法繞過的就是MQTT協議,什么是MQTT協議呢?
MQTT(Message Queuing Telemetry Transport,消息隊列遙測傳輸)是IBM開發的一個即時通訊協議。
國內很多企業都廣泛使用MQTT作為Android手機客戶端與服務器端推送消息的協議。其中Sohu,Cmstop手機客戶端中均有使用到MQTT作為消息推送消息。
MQTT由於開放源代碼,耗電量小等特點,將會在移動消息推送領域會有更多的貢獻,在物聯網領域,傳感器與服務器的通信,信息的收集,MQTT都可以作為考慮的方案之一。在未來MQTT會進入到我們生活的各各方面。 

二、MQTT特點

是輕量級基於代理的發布/訂閱的消息傳輸協議,設計思想是開放、簡單、輕量、易於實現。這些特點使它適用於受限環境。例如:

網絡代價昂貴,帶寬低、不可靠。在嵌入設備中運行,處理器和內存資源有限的場景。

該協議的特點有:

  • 使用發布/訂閱消息模式,提供一對多的消息發布,解除應用程序耦合。
  • 對負載內容屏蔽的消息傳輸。
  • 使用 TCP/IP 提供網絡連接。

有三種消息發布服務質量:

  • "至多一次",消息發布完全依賴底層 TCP/IP 網絡。會發生消息丟失或重復。這一級別可用於如下情況,環境傳感器數據,丟失一次讀記錄無所謂,因為不久后還會有第二次發送。
  • "至少一次",確保消息到達,但消息重復可能會發生。
  • "只有一次",確保消息到達一次。這一級別可用於如下情況,在計費系統中,消息重復或丟失會導致不正確的結果。
  • 小型傳輸,開銷很小(固定長度的頭部是 2 字節),協議交換最小化,以降低網絡流量。
  • 使用 Last Will 和 Testament 特性通知有關各方客戶端異常中斷的機制。

在未來幾年,MQTT的應用會越來越廣,值得關注。

通過MQTT協議,目前已經擴展出了數十個MQTT服務器端程序,可以通過PHP,JAVA,Python,C,C#等系統語言來向MQTT發送相關消息。

此外,國內很多企業都廣泛使用MQTT作為Android手機客戶端與服務器端推送消息的協議。其中Sohu,Cmstop手機客戶端中均有使用到MQTT作為消息推送消息。 

三、在Android推送方面與其他協議對比

1、使用XMPP協議(Openfire + Spark + Smack)
簡介:基於XML協議的通訊協議,前身是Jabber,目前已由IETF國際標准化組織完成了標准化工作。
優點:協議成熟、強大、可擴展性強、目前主要應用於許多聊天系統中,且已有開源的Java版的開發實例androidpn。
缺點:協議較復雜、冗余(基於XML)、費流量、費電,部署硬件成本高。

2、使用MQTT協議(更多信息見:http://mqtt.org/
簡介:輕量級的、基於代理的“發布/訂閱”模式的消息傳輸協議。
優點:協議簡潔、小巧、可擴展性強、省流量、省電,目前已經應用到企業領域(參考:http://mqtt.org/software)。
缺點:不夠成熟、實現較復雜、服務端組件rsmb不開源,部署硬件成本較高。

3、使用HTTP輪循方式
簡介:定時向HTTP服務端接口(Web Service API)獲取最新消息。
優點:實現簡單、可控性強,部署硬件成本低。
缺點:實時性差。

4、使用GCM服務(Google Cloud Messaging)
簡介:Google推出的雲消息服務,即第二代的C2DM。
優點:Google提供的服務、原生、簡單,無需實現和部署服務端。
缺點:Android版本限制(必須大於2.2版本),該服務在國內不夠穩定、需要用戶綁定Google帳號,受限於Google。

 

四、詳細解釋MQTT協議

固定頭部

固定頭部,使用兩個字節,共16位:

bit 7 6 5 4 3 2 1 0
byte 1 Message Type DUP flag QoS level RETAIN
byte 2 Remaining Length

第一個字節(byte 1)

消息類型(4-7),使用4位二進制表示,可代表16種消息類型:

Mnemonic Enumeration Description
Reserved 0 Reserved
CONNECT 1 Client request to connect to Server
CONNACK 2 Connect Acknowledgment
PUBLISH 3 Publish message
PUBACK 4 Publish Acknowledgment
PUBREC 5 Publish Received (assured delivery part 1)
PUBREL 6 Publish Release (assured delivery part 2)
PUBCOMP 7 Publish Complete (assured delivery part 3)
SUBSCRIBE 8 Client Subscribe request
SUBACK 9 Subscribe Acknowledgment
UNSUBSCRIBE 10 Client Unsubscribe request
UNSUBACK 11 Unsubscribe Acknowledgment
PINGREQ 12 PING Request
PINGRESP 13 PING Response
DISCONNECT 14 Client is Disconnecting
Reserved 15 Reserved

除去0和15位置屬於保留待用,共14種消息事件類型。

DUP flag(打開標志)

保證消息可靠傳輸,默認為0,只占用一個字節,表示第一次發送。不能用於檢測消息重復發送等。只適用於客戶端或服務器端嘗試重發PUBLISH, PUBREL, SUBSCRIBE 或 UNSUBSCRIBE消息,注意需要滿足以下條件:

 當QoS > 0 消息需要回復確認 

此時,在可變頭部需要包含消息ID。當值為1時,表示當前消息先前已經被傳送過。

QoS(Quality of Service,服務質量)

使用兩個二進制表示PUBLISH類型消息:

QoS value bit 2 bit 1 Description
0 0 0 至多一次 發完即丟棄 <=1
1 0 1 至少一次 需要確認回復 >=1
2 1 0 只有一次 需要確認回復 =1
3 1 1 待用,保留位置

RETAIN(保持)

僅針對PUBLISH消息。不同值,不同含義:

1:表示發送的消息需要一直持久保存(不受服務器重啟影響),不但要發送給當前的訂閱者,並且以后新來的訂閱了此Topic name的訂閱者會馬上得到推送。

備注:新來乍到的訂閱者,只會取出最新的一個RETAIN flag = 1的消息推送。

0:僅僅為當前訂閱者推送此消息。

假如服務器收到一個空消息體(zero-length payload)、RETAIN = 1、已存在Topic name的PUBLISH消息,服務器可以刪除掉對應的已被持久化的PUBLISH消息。

Remaining Length(剩余長度)

在當前消息中剩余的byte(字節)數,包含可變頭部和負荷(內容)。

單個字節最大值:01111111,16進制:0x7F,10進制為127。

MQTT協議規定,第八位(最高位)若為1,則表示還有后續字節存在。

MQTT協議最多允許4個字節表示剩余長度。最大長度為:0xFF,0xFF,0xFF,0x7F,二進制表示為:11111111,11111111,11111111,01111111,十進制:268435455 byte=261120KB=256MB=0.25GB 四個字節之間值的范圍:

Digits From To
1 0 (0x00) 127 (0x7F)
2 128 (0x80, 0x01) 16 383 (0xFF, 0x7F)
3 16 384 (0x80, 0x80, 0x01) 2 097 151 (0xFF, 0xFF, 0x7F)
4 2 097 152 (0x80, 0x80, 0x80, 0x01) 268 435 455 (0xFF, 0xFF, 0xFF, 0x7F)

其實換個方式理解:第1字節的基數是1,而第2字節的基數:128,以此類推,第三字節的基數是:128*128=2的14次方,第四字節是:128*128*128=2的21次方;

例如,需要表達321=2*128+65.(2字節):10100001 0000 0011.

(和我們理解的低位運算放置順序不一樣,第一個字節是低位,后續字節是高位,但字節內部本身是低位右邊,高位左邊)。

可變頭部

固定頭部僅定義了消息類型和一些標志位,一些消息的元數據,需要放入可變頭部中。可變頭部內容字節長度 + Playload/負荷字節長度 = 剩余長度。

可變頭部,包含了協議名稱,版本號,連接標志,用戶授權,心跳時間等內容。

可變頭部居於固定頭部和payload中間。

可變剩余長度(remaing length)不是可變頭部的一部分,當然該長度值也是從可變頭部開始計算,包含可變頭部的長度+payload的長度。

可變頭部的字段如下:

協議名稱: MQTT CONNECT message. UTF編碼:如 MQIsdp, capitalized.

協議版本:8位無符號,當前使用:3 (0x03),如下:

bit 7 6 5 4 3 2 1 0
  Protocol Version
  0 0 0 0 0 0 1 1

Connect flags

Clean session, Will, Will QoS, Retain flags 該字段的設置

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

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

 

Clean session flag

Position: bit 1 ,連接標志.

0:server需要存儲client的訂閱。包括存儲Qos 1和2的訂閱主題(當client重連時能將消息發送);當連接丟失的時候 服務器必須維護正在發送的消息的狀態直到客戶端重新連接到服務器。

1:server MUST忽略之前維護關於client的信息,並且將該connection當成clean的。server MUST 忽略任何client斷開的狀態。

 

原文翻譯不能,所以參考了下一位大牛的表達:

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

Will Flag

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

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

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

Will Qos

兩位表示,和PUBLISH消息固定頭部的QoS level含義一樣。

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

Will RETAIN

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

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

User name 和 password Flag:

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

 

bit 7 6 5 4 3 2 1 0
  User Name Flag Password Flag Will Retain Will QoS Will Flag Clean Session Reserved
  x x x x x x   x

 

Playload/消息體/負荷

消息體主要是為配合固定/可變頭部命令(比如CONNECT可變頭部User name標記若為1則需要在消息體中附加用戶名稱字符串)而存在。

CONNECT/SUBSCRIBE/SUBACK/PUBLISH等消息有消息體。PUBLISH的消息體以二進制形式對待。

MQTT協議只允許在PUBLISH類型消息體中使用自定義特性,在固定/可變頭部想加入自定義私有特性是不允許的。

這也是為了協議免於流於形式,變得很分裂也為了兼顧現有客戶端等。比如支持壓縮等,那就可以在Playload中定義數據支持,在應用中進行讀取處理。

這部分會在后面詳細論述。

消息標識符/消息ID

固定頭中的QoS level標志值為1或2時才會在:PUBLISH,PUBACK,PUBREC,PUBREL,PUBCOMP,SUBSCRIBE,SUBACK,UNSUBSCRIBE,UNSUBACK等消息的可變頭中出現。

一個16位無符號位的short類型值(值不能為 0,0做保留作為無效的消息ID),僅僅要求在一個特定方向(服務器發往客戶端為一個方向,客戶端發送到服務器端為另一個方向)的通信消息中必須唯一。比如客戶端發往服務器,有可能存在服務器發往客戶端會同時存在重復,但不礙事。

可變頭部中,需要兩個字節的順序是MSB(Most Significant Bit) LSB(Last/Least Significant Bit),翻譯成中文就是,最高有效位,最低有效位。最高有效位在最低有效位左邊/上面,表示這是一個大端字節/網絡字節序,符合人的閱讀習慣,高位在最左邊。

bit 7 6 5 4 3 2 1 0
  Message Identifier MSB
  Message Identifier LSB

最大長度可為: 65535

UTF-8編碼

有關字符串,MQTT采用的是修改版的UTF-8編碼,一般形式為如下:

bit 7 6 5 4 3 2 1 0
byte 1 String Length MSB
byte 2 String Length LSB
bytes 3 ... Encoded Character Data

 

 最后的結構如下:

 

  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
Playload/消息體

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個字符或者更少,但不是必須。

 

心跳時間(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連接。

 

參考大神博客:http://www.blogjava.net/yongboy/archive/2014/02/09/409630.html


免責聲明!

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



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