MQTT 協議是個啥?這篇文章告訴你!
文章首發於我的公眾號「程式設計師cxuan」,歡迎大家關注呀~
之前有位讀者給我留言說想要了解一下什麼是 MQTT 協議,順便還把我誇了一把,有點不好意思啦。
那麼讀者的要求必須要滿足啊,所以現在 @一下這位小姐姐,來聽課啦!
什麼是 MQTT 協議
MQTT 協議的全稱是 Message Queuing Telemetry Transport,翻譯為消息隊列傳輸探測,它是 ISO 標準下的一種基於發布 – 訂閱模式的消息協議,它是基於 TCP/IP 協議簇的,它是為了改善網路設備硬體的性能和網路的性能來設計的。MQTT 一般多用於 IoT 即物聯網上,廣泛應用於工業級別的應用場景,比如汽車、製造、石油、天然氣等。
在了解了 MQTT 的概念和應用場景後,我們下來就來走進 MQTT 的學習中了,先來看一下 MQTT 有哪些概念。
MQTT 基礎
上面我們解釋了 MQTT 協議的基本概念,MQTT 協議總結一點就是一種輕量級的二進位協議,MQTT 協議與 HTTP 相比具有一個明顯的優勢:數據包開銷較小,數據包開銷小就意味著更容易進行網路傳輸。還有一個優勢就是 MQTT 在客戶端容易實現,而且具有易用性,非常適合當今資源有限的設備。
你可能對這些概念有些諱莫如深,為什麼具有 xxx 這種特性呢?這就需要從 MQTT 的設計說起了。
MQTT 協議由 Andy Stanford-Clark (IBM) 和 Arlen Nipper(Arcom,現為 Cirrus Link)於 1999 年發明。 他們需要一種通過衛星連接石YouTube道的協議,以最大限度地減少電池損耗和頻寬。所以他們為這個協議規定了幾種要求:
- 這個協議必須易於實現;
- 這個協議中的數據必須易於傳輸,消耗成本小;
- 這個協議必須提供服務品質管理;
- 這個協議必須支援連續的會話控制
- 假設數據不可知,不強求傳輸數據的類型與格式,保持靈活性。
這些設計也是 MQTT 的精髓所在,MQTT 經過不斷的發展,已經成為了物聯網 IoT 所必備的一種消息探測協議,官方強烈推薦使用的版本是 MQTT 5。
發布 – 訂閱模式
發布 – 訂閱模式我相信接觸消息中間件架構的同學都聽過,這是一種傳統的客戶端 – 伺服器架構的替代方案,因為一般傳統的客戶端-伺服器是客戶端能夠直接和伺服器進行通訊。
但是發布 – 訂閱模式 pub/sub
就不一樣了,發布訂閱模式會將發送消息的發布者 publisher
與接收消息的訂閱者 subscribers
進行分離,publisher 與 subscribers 並不會直接通訊,他們甚至都不清楚對方是否存在,他們之間的交流由第三方組件 broker
代理。
pub/sub 最重要的方面是 publisher 與 subscriber 的解藕,這種耦合度有下面三個維度:
- 空間解耦:publisher 與 subscriber 並不知道對方的存在,例如不會有 IP 地址和埠的交互,也更不會有消息的交互。
- 時間解藕:publisher 與 subscriber 並不一定需要同時運行。
- 同步
Synchronization
解藕:兩個組件的操作比如 publish 和 subscribe 都不會在發布或者接收過程中產生中斷。
總之,發布/訂閱模式消除了傳統客戶-伺服器之間的直接通訊,把通訊這個操作交給了 broker 進行代理,並在空間、時間、同步三個維度上進行了解藕。
可拓展性
pub/sub 比傳統的客戶端-伺服器模式有了更好的拓展,這是由於 broker 的高度並行化
,並且是基於事件驅動
的模式。可拓展性還體現在消息的快取和消息的智慧路由,還可以通過集群代理來實現數百萬的連接,使用負載均衡器將負載分配到更多的單個伺服器上,這就是 MQTT 的深度應用了。
你可能不明白什麼是事件驅動,我在這裡解釋下事件驅動的概念。
事件驅動是一種編程範式
,編程範式是軟體工程中的概念,它指的是一種編程方法或者說程式設計方式,比如說面向對象編程和面向過程編程就是一種編程範式,事件驅動中的程式流程會由諸如用戶操作(點擊滑鼠、鍵盤)、感測器輸出或者從其他程式或傳遞的消息事件決定。事件驅動編程是圖形用戶介面和其他應用程式比如 Web 中使用的主要範式,這些應用程式能夠響應用戶輸入執行某些操作為中心,這同時也適用於驅動程式的編程。
消息過濾
在 pub/sub 的架構模式中,broker 扮演著至關重要的作用,其中非常重要的一點就是 broker 能夠對消息進行過濾,使每個訂閱者只接收自己感興趣的消息。
broker 有幾個可以過濾的選項
- 基於主題的過濾
MQTT 是基於 subject 的消息過濾的,每條消息都會有一個 topic ,接收客戶端會向 borker 訂閱感興趣的 topic,訂閱後,broker 就會確保客戶端收到發布到 topic 中的消息。
- 基於內容的過濾
在基於內容的過濾中,broker 會根據特定的內容過濾消息,接受客戶端會經過過濾他們感興趣的內容。這種方法的一個顯著的缺點就是必須事先知道消息的內容,不能加密或者輕易修改。
- 基於類型的過濾
在使用面向對象的語言時,基於消息(事件)的類型過濾是一種比較常見的過濾方式。
為了發布/訂閱系統的挑戰,MQTT 具有三個服務品質級別,你可以指定消息從客戶端傳到 broker 或者從 broker 傳到客戶端,在 topic 的訂閱中,會存在 topic 沒有 subscriber 訂閱的情況,作為 broker 必須知道如何處理這種情況。
MQTT 與消息隊列的區別
我們現在知道,MQTT 是一種消息隊列傳輸探測協議,這種協議是看似是以消息隊列為基礎,但卻與消息隊列有所差別。
在傳統的消息隊列模式中,一條消息會存儲在消息隊列中等待被消費,每個傳入的消息都存儲在消息隊列中,直到它被客戶端(通常稱之為消費者)所接收,如果沒有客戶端消費消息的話,這條消息就會存在消息隊列中等待被消費。但是在消息隊列中,不會存在消息沒有客戶端消費的情況,但是在 MQTT 中,確存在 topic 無 subscriber 訂閱的情況。
在傳統的消息隊列模式中,一條消息只能被一個客戶端所消費,負載
會分布在隊列的每個消費者之間;而在 MQTT 中,每個訂閱者都會受到消息,每個訂閱者有相同的負載。
在傳統的消息隊列模式中,必須使用單獨的命令來顯式創建隊列,只有隊列創建後,才可以生產或者消費消息;而在 MQTT 中,topic 比較靈活,可以即時創建。
HiveMQ 現在是開源的,HiveMQ 社區版實現了 MQTT broker 規範,併兼容了 MQTT 3.1、3.1.1 和 MQTT 5。HiveMQ MQTT Client 是一個基於 Java 的 MQTT 客戶端實現,兼容 MQTT 3.1.1 和 MQTT 5。這兩個項目都可以在 HiveMQ 的 github //github.com/hivemq 上找到。
我們知道,broker 將 publisher 和 subscriber 進行分離,因此客戶端的連接由 broker 代理,所以在我們深入理解 MQTT 之前,我們需要先知道客戶端和代理的含義。
MQTT 重要概念
MQTT client
當我們討論關於客戶端的概念時,一般指的就是 MQTT Client,publisher 和 subscriber 都屬於 MQTT Client。之所以有發布者和訂閱者這個概念,其實是一種相對的概念,就是指當前客戶端是在發布消息還是在接收消息,發布和訂閱的功能也可以由同一個 MQTT Client 實現。
MQTT 客戶端是指運行 MQTT 庫並通過網路連接到 MQTT broker 的任何設備,這些設備可以從微控制器到成熟的伺服器。基本上,任何使用 TCP/IP 協議使用 MQTT 設備的都可以稱之為 MQTT Client。MQTT 協議的客戶端實現非常簡單直接。易於實施是 MQTT 非常適合小型設備的原因之一。 MQTT 客戶端庫可用於多種程式語言。 例如,Android、Arduino、C、C++、C#、Go、iOS、Java、JavaScript 和 .NET。
MQTT broker
與 MQTT client 對應的就是 MQTT broker,broker 是任何發布/訂閱機構的核心,根據實現的不同,代理可以處理多達數百萬連接的 MQTT client。
broker 負責接收所有消息,過濾消息,確定是哪個 client 訂閱了每條消息,並將消息發送給對應的 client,broker 還負責保存會話數據,這些數據包括訂閱的和錯過的消息。broker 還負責客戶端的身份驗證和授權。
MQTT Connection
MQTT 是基於 TCP/IP 協議基礎之上的,所以 MQTT 的 client 和 broker 都需要 TCP/IP 協議的支援。
MQTT 的連接總是在 client 和 broker 之間進行,client 和 client 之間並不會相互連接。如果要發起連接的話,那麼 client 就會向 broker 發起 CONNECT
消息,代理會使用 CONNACK
消息和狀態碼進行響應。一旦 client 和 broker 的連接建立後,broker 就會使客戶端的連接一直處於打開狀態,直到 client 發出斷開命令或者連接中斷。
消息報文
MQTT 的消息報文主要分為 CONNECT 和 CONNACK 消息。
CONNECT
我們上面提到了為了初始化連接,需要 client 向 broker 發送 CONNECT 消息,如果這個 CONNECT 消息格式錯誤或者打開套接字(因為基於 TCP/IP 協議棧需要初始化 Socket 連接)時間過長,亦或是發送連接消息時間過長的話,broker 就會關閉這條連接。
一個 MQTT 客戶端發送一條 CONNECT 連接,這條 CONNECT 連接可能會包含下面這些資訊:
我這裡解釋一下這些資訊都是什麼概念
ClientId
:顯而易見,這個就是每個客戶端的 ID 標識,也就是連接到 MQTT broker 的每個 client。這個 ID 應該是每個 client 和 broker 唯一的,如果你不需要 broker 持有狀態的話,你可以發送一個空的 ClientId,空的 ClientId 會沒有任何狀態。在這種情況下,ClientSession 需要設置為 true,否則將會拒絕連接。
clientSession 是什麼我們下面會說。
CleanSession
:CleanSession 會話標誌會告訴 broker client 是否需要建立持久會話。在持久會話 (CleanSession = false)中,broker 存儲 client 的所有訂閱以及服務品質(Qos) 是 1 或 2 訂閱的 client 的所有丟失的消息。如果會話不是持久的(CleanSession = true),那麼 broker 則不會為 client 存儲任何內容並且會清除先前持久會話中的所有資訊。Username/Password
:MQTT 會發送 username 和 password 進行 client 認證和授權。如果此資訊沒有經過加密或者 hash ,那麼密碼將會以純文本的形式發送。所以,一般強烈建議 username 和 password 要經過加密安全傳輸。像 HiveMQ 這樣的 broker 可以與 SSL 證書進行身份驗證,因此不需要用戶名和密碼。LastWillxxx
:LastWillxxx 表示的是遺願,client 在連接 broker 的時候將會設立一個遺願,這個遺願會保存在 broker 中,當 client 因為非正常原因斷開與 broker 的連接時,broker 會將遺願發送給訂閱了這個 topic(訂閱遺願的 topic)的 client。keepAlive
:keepAlive 是 client 在連接建立時與 broker 通訊的時間間隔,通常以秒為單位。這個時間指的是 client 與 broker 在不發送消息下所能承受的最大時長。
在聊完 client 與 broker 之間發送建立連接的 CONNECT 消息後,我們再來聊一下 broker 需要對 CONNECT 進行確認的 CONNACK 消息。
CONNACK
當 broker 收到 CONNECT 消息時,它有義務回復 CONNACK 消息進行響應。CONNACK 消息包括兩部分內容
-
SessionPresent
:會話當前標識,這個標誌會告訴 client 當前 broker 是否有一個持久性會話與 client 進行交互。SessionPresent 標誌和 CleanSession 標誌有關,當 client 在 CleanSession 設置為 true 的情況下連接時,SessionPresent 始終為 false,因為沒有持久性會話可以使用。如果 CleanSession 設置為 false,則有兩種可能性,如果 ClientId 的會話資訊可用,並且 broker 已經存儲了會話資訊,那麼 SessionPresent 為 true,否則如果沒有 ClientId 的任何會話資訊,那麼 SessionPresent 為 false。 -
ReturnCode
:CONNACK 消息中的第二個標誌是連接確認標誌。這個標誌包含一個返回碼,告訴客戶端連接嘗試是否成功。連接確認標誌有下面這些選項。
關於每個連接的詳細說明,可以參考 //docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718035
消息類型
發布
當 MQTT client 在連接到 broker 之後就可以發送消息了,MQTT 使用的是基於 topic 主題的過濾。每條消息都應該包含一個 topic ,broker 可以使用 topic 將消息發送給感興趣的 client。除此之外,每條消息還會包含一個負載(Payload)
,Payload 中包含要以位元組形式發送的數據。
MQTT 是數據無關性的,也就是說數據是由發布者 – publisher 決定要發送的是 XML 、JSON 還是二進位數據、文本數據。
MQTT 中的 PUBLISH 消息結構如下。
Packet Identifier
:這個 PacketId 標識在 client 和 broker 之間唯一的消息標識。packetId 僅與大於零的 Qos 級別相關。TopicName
:主題名稱是一個簡單的字元串,/
代表著分層結構。Qos
:這個數字表示的是服務品質水平,服務品質水平有三個等級:0、1 和 2,服務級別決定了消息到達 client 或者 broker 的保證類型,來決定消息是否丟失。RetainFlag
:這個標誌表示 broker 將最近收到的一條 RETAIN 標誌位為true
的消息保存在伺服器端(記憶體或者文件)。
MQTT 伺服器只會為每一個 Topic 保存最近收到的一條 RETAIN 標誌位為
true
的消息。也就是說,如果MQTT 伺服器上已經為某個 Topic 保存了一條 Retained 消息,當客戶端再次發布一條新的 Retained 消息時,那麼伺服器上原來的那條消息會被覆蓋。
Payload
:這個是每條消息的實際內容。MQTT 是數據無關性的。可以發送任何文本、影像、加密數據以及二進位數據。Dupflag
:這個標誌表示該消息是重複的並且由於預期的 client 或者 broker 沒有確認所以重新發送了一次。這個標誌僅僅與 Qos 大於 0 相關。
當 client 向 broker 發送消息時,broker 會讀取消息,根據 Qos 的級別進行消息確認,然後處理消息。處理消息其實就是確定哪些 subscriber 訂閱了 topic 並將消息發送給他們。
最初發布消息的 client 只關心將 PUBLISH 消息發送給 broker,一旦 broker 收到 PUBLISH 消息,broker 就有責任將其傳遞給所有 subscriber。發布消息的 client 不會知道是否有人對發布的消息感興趣,同時也不知道多少 client 從 broker 收到了消息。
訂閱
client 會向 broker 發送 SUBSCRIBE 消息來接收有關感興趣的 topic,這個 SUBSCRIBE 消息非常簡單,它包含了一個唯一的數據包標識和一個訂閱列表。
Packet Identifier
:這個 PacketId 和上面的 PacketId 一樣,都表示消息的唯一標識符。ListOfSubscriptions
:SUBSCRIBE 消息可以包含一個 client 的多個訂閱,每個訂閱都會由一個 topic 和一個 Qos 構成。訂閱消息中的 topic 可以包含通配符。
確認消息
client 在向 broker 發送 SUBSCRIBE 消息後,為了確認每個訂閱,broker 會向 client 發送 SUBACK 確認消息。這個 SUBACK 包含原始 SUBSCRIBE 消息的 packetId 和返回碼列表。
其中
Packet Identifier
:這個數據包標識符和 SUBSCRIBE 中的相同。ReturnCode
:broker 為每個接收到的 SUBSCRIBE 消息的 topic/Qos 對發送一個返回碼。例如,如果 SUBSCRIBE 消息有五個訂閱消息,則 SUBACK 消息包含五個返回碼作為響應。
到現在我們已經探討過了三種消息類型,發布 – 訂閱 – 確認消息,這三種消息的示意圖如下。
退訂
SUBSCRIBE 消息對應的是 UNSUBSCRIBE
消息,這條消息發送後,broker 會刪除關於 client 的訂閱。所以,UNSUBSCRIBE 消息與 SUBSCRIBE 消息類似,都具有 packetId 和 topic 列表。
確認退訂
取消訂閱也需要 broker 的確認,此時 broker 會向 client 發送一個 UNSUBACK
消息,這個 UNSUBACK 消息非常簡單,只有一個 packetId 數據標識符。
退訂和確認退訂的流程如下。
當 client 收到來自 broker 的 UNSUBACK 消息後,就可以認為 UNSUBSCRIBE 消息中的訂閱被刪除了。
聊聊 Topic
聊了這麼多關於 MQTT 的內容,但是我們還沒有好好聊過 Topic。在 MQTT 中,Topic 是指 broker 為每個連接的 client 過濾消息的 UTF-8
字元串。Topic 是一種分層的結構,可以由一個或者多個 Topic 組成。每個 Topic 由 /
進行分割。
與傳統的消息隊列相比,MQTT Topic 非常輕量級,client 在發布或訂閱之前不需要先創建所需要的 Topic,broker 在接收每個 Topic 前不用進行初始化操作。
通配符
當客戶端訂閱 Topic 時,它可以訂閱已發布消息的確切 Topic,也可以使用通配符來同時訂閱多個 Topic。通配符有兩種:單級和多級。
單級通配符
單級通配符可以替換 Topic 的一個級別,+
號代表 Topic 中的單級通配符。
如果 Topic 包含任意字元串而不是通配符,則任何 Topic 都能夠和單級通配符匹配。例如
myhome/groundfloor/+/temperature 就有下面這幾種匹配方式。
多級通配符
多級通配符涵蓋多個 Topic,#
代表 Topic 中的多級通配符。為了讓 broker 能夠確定和哪些 Topic 匹配,多級通配符必須作為 Topic 中的最後一個字元放置,並以 /
開頭。
下面是 myhome/groundfloor/# 的幾個例子
當 client 訂閱帶有多級通配符的 Topic 時,不論 Topic 有多長多深,它都會收到通配符之前 Topic 的所有消息。如果你只將 Topic 定義為 # 的話,那麼你將會收到所有的消息。
我自己肝了六本 PDF,全網傳播超過10w+ ,微信搜索「程式設計師cxuan」關注公眾號後,在後台回復 cxuan ,領取全部 PDF,這些 PDF 如下