MQTT協議的基礎概念
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 訊息通訊流程如下所示:

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