MQTT 協議是個啥?這篇文章告訴你!

文章首發於我的公眾號「程式設計師cxuan」,歡迎大家關注呀~

說到做到!

之前有位讀者給我留言說想要了解一下什麼是 MQTT 協議,順便還把我誇了一把,有點不好意思啦。

那麼讀者的要求必須要滿足啊,所以現在 @一下這位小姐姐,來聽課啦!

什麼是 MQTT 協議

MQTT 協議的全稱是 Message Queuing Telemetry Transport,翻譯為消息隊列傳輸探測,它是 ISO 標準下的一種基於發布 – 訂閱模式的消息協議,它是基於 TCP/IP 協議簇的,它是為了改善網路設備硬體的性能和網路的性能來設計的。MQTT 一般多用於 IoT 即物聯網上,廣泛應用於工業級別的應用場景,比如汽車、製造、石油、天然氣等。

img

在了解了 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 如下

六本 PDF 鏈接

Tags: