1、MQTT協議簡介
MQTT 是什么
MQTT 的全稱為 Message Queue Telemetry Transport,是在 1999 年,由 IBM 的 Andy Stanford-Clark 和 Arcom 的 Arlen Nipper 為了一個通過衛星網絡連接輸油管道的項目開發的。為了滿足低電量消耗和低網絡帶寬的需求,MQTT 協議在設計之初就包含了以下一些特點:
- 實現簡單
- 提供數據傳輸的 QoS
- 輕量、占用帶寬低
- 可傳輸任意類型的數據
- 可保持的會話(session)
之后 IBM 一直將 MQTT 作為一個內部協議在其產品中使用,直到 2010 年,IBM 公開發布了 MQTT 3.1 版本。在 2014 年,MQTT 協議正式成為了 OASIS(結構化信息標準促進組織)的標準協議。隨著多年的發展,MQTT 協議的重點也不再只是嵌入式系統,而是更廣泛的物聯網(Internet of Things)世界了。
MQTT 協議是什么?簡單地來說 MQTT 協議有以下特性:
- 基于 TCP 協議的應用層協議;
- 采用 C/S 架構;
- 使用訂閱/發布模式,將消息的發送方和接受方解耦;
- 提供 3 種消息的 QoS(Quality of Service): 至多一次,最少一次,只有一次;
- 收發消息都是異步的,發送方不需要等待接收方應答。
雖然 MQTT 協議名稱有 Message Queue 兩個詞,但是它并不是一個像 RabbitMQ 那樣的一個消息隊列,這是初學者最容易搞混的一個問題。MQTT 跟傳統的消息隊列相比,有以下一些區別:
- 在傳統消息隊列中,在發送消息之前,必須先創建相應的隊列;在 MQTT 中,不需要預先創建要發布的主題(可訂閱的 Topic);
- 在傳統消息隊列中,未被消費的消息總是會被保存在某個隊列中,直到有一個消費者將其消費;在 MQTT 中,如果發布一個沒有被任何客戶端訂閱的消息,這個消息將被直接扔掉;
- 在傳統消息隊列中,一個消息只能被一個客戶端獲取,在 MQTT 中,一個消息可以被多個訂閱者獲取,MQTT 協議也不支持指定消息被單一的客戶端獲取。
- MQTT 協議可以為大量的低功率、工作網絡環境不可靠的物聯網設備提供通訊保障。而它的應用范圍也不僅如此,在移動互聯網領域也大有作為:很多 Android App 的推送功能,都是基于 MQTT 實現的,也有一些 IM 的實現,是基于 MQTT 的。
2、MQTT協議的通信模型
MQTT的通信是通過發布/訂閱的方式來實現的,消息的發布方和訂閱方通過這種方式來進行解耦,它們沒有直接的連接,它們需要一個中間方。在 MQTT 里面我們稱之為 Broker,用來進行消息的存儲和轉發。一次典型的 MQTT 消息通信流程如下所示:
- 發布方將消息發送到Broker
- Broker 接受到消息以后,檢查下都有哪些訂閱方訂閱了此類消息,然后將消息發送到這些訂閱方
- 訂閱方從 Broker 獲取該消息
3、MQTT Client
任何終端、服務器、嵌入式設備等只要運行了MQTT的庫或者代碼,我們都可以稱之為MQTT Client。當然,任何的發布方(Publisher)和訂閱方(Subscriber)也都是Client,Publisher 或者 Subscriber 只取決于該 Client 當前的狀態——是在發布還是在訂閱消息,并且一個Client可以同時是Publisher 或者 Subscriber。
MQTT Client 庫在很多語言中都有實現,包括 Android、Arduino、Ruby、C、C++、C#、Go、iOS、Java、JavaScript,以及 .NET 等。如果你要查看相應語言的庫實現,可以在這里找到。
4、MQTT Broker
Broker 是整個MQTT 發布/訂閱 的核心,它接收 Publisher 發布的消息,并把消息傳遞給訂閱過此消息主題的 Subscriber。在實際應用中,一個 MQTT Broker 還應該提供以下一些功能:
- 可以橫向擴展,比如集群,來滿足大量的 Client 接入
- 可以擴展接入業務系統
- 易于監控,滿足高可用性
在國內我們可以使用騰訊云、阿里云、青云之類的云服務商提供的MQTT 服務
5、MQTT 協議數據包
MQTT 協議的數據包格式非常簡單,一個 MQTT 協議數據包由下面三個部分組成:
- 固定頭(Fixed header):存在于所有的 MQTT 數據包中,用于表示數據包類型及對應標識,表明數據包大小;
- 可變頭(Variable header):存在于部分類型的 MQTT 數據包中,具體內容由相應類型的數據包決定;
- 消息體(Payload):存在于部分 MQTT 數據包中,存儲消息的具體數據。
接下來看一下固定頭的格式:
Bit | | 7 | 6 | 5 | 4 | | | 3 | 2 | 1 | 0 | |
---|---|---|
字節 1 | MQTT 數據包類型 | MQTT 數據包 Flag,內容由數據包類型指定 |
字節 2…… | 數據包剩余長度 | --- |
固定頭的第一個字節的高 4 位 bit 用于指定該數據包的類型,MQTT 的數據包有以下一些類型:
名稱 | 值 | 方向 | 描述 |
---|---|---|---|
Reserved | 0 | 不可用 | 保留位 |
CONNECT | 1 | Client 到 Broker | Client 請求連接到 Broker |
CONNACK | 2 | Broker 到 Client | 連接確認 |
PUBLISH | 3 | 雙向 | 發布消息 |
PUBACK | 4 | 雙向 | 發布確認 |
PUBREC | 5 | 雙向 | 發布收到 |
PUBREL | 6 | 雙向 | 發布釋放 |
PUBCOMP | 7 | 雙向 | 發布完成 |
SUBSCRIBE | 8 | Client 到 Broker | Client 請求訂閱 |
SUBACK | 9 | Broker 到 Client | 訂閱確認 |
UNSUBSCRIBE | 10 | Client 到 Broker | Client 請求取消訂閱 |
UNSUBACK | 11 | Broker 到 Client | 取消訂閱確認 |
PINGREQ | 12 | Client 到 Broker | PING 請求 |
PINGRESP | 13 | Broker 到 Client | PING 應答 |
DISCONNECT | 14 | Client 到 Broker | Client 主動中斷連接 |
Reserved | 15 | 不可用 | 保留位 |
固定頭的低 4 位 bit 用于指定數據包的 Flag,不同的數據包類型,其 Flag 的定義是不一樣的,每種數據包對應的 Flag 如下:
數據包 | 標識位 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
---|---|---|---|---|---|
CONNECT | 保留位 | 0 | 0 | 0 | 0 |
CONNACK | 保留位 | 0 | 0 | 0 | 0 |
PUBLISH | MQTT 3.1.1 使用 | DUP | QoS | QoS | RETAIN |
PUBACK | 保留位 | 0 | 0 | 0 | 0 |
PUBREC | 保留位 | 0 | 0 | 0 | 0 |
PUBREL | 保留位 | 0 | 0 | 0 | 0 |
PUBCOMP | 保留位 | 0 | 0 | 0 | 0 |
SUBSCRIBE | 保留位 | 0 | 0 | 0 | 0 |
SUBACK | 保留位 | 0 | 0 | 0 | 0 |
UNSUBSCRIBE | 保留位 | 0 | 0 | 0 | 0 |
UNSUBACK | 保留位 | 0 | 0 | 0 | 0 |
PINGREQ | 保留位 | 0 | 0 | 0 | 0 |
PINGRESP | 保留位 | 0 | 0 | 0 | 0 |
DISCONNECT | 保留位 | 0 | 0 | 0 | 0 |
從固定頭的第 2 字節開始是用于標識 MQTT 數據包長度的字段,最少一個字節,最大四個字節,每一個字節的低 7 位用于標識值,范圍為 0~127。最高位的 1 位是標識位,用來說明是否有后續字節來標識長度。例如:標識為 0,代表為沒有后續字節;標識為 1,代表后續還有一個字節用于標識包長度。MQTT 協議規定最多可以用四個字節來標識包長度。
所以這四個字節最多可以標識的包長度為:(0xFF, 0xFF, 0xFF, 0x7F) = 268435455 字節,約 256M,這個是 MQTT 協議中數據包的最大長度。
注意: 1、Remain Length 的值不包含固定頭的大小,包括第 1 字節和 Remain Length 字段。 2、本文章中 MQTT 協議版本為 3.1.1