切換語言為:簡體

MQTT協議詳解及常見問題解答

  • 爱糖宝
  • 2024-06-04
  • 2083
  • 0
  • 0

1. 概述

MQTT(Message Queuing Telemetry Transport,訊息佇列遙測傳輸協議),是一種基於釋出/訂閱(publish/subscribe)模式的"輕量級"通訊協議,該協議構建於TCP/IP協議上。

基本原理如下圖所示

MQTT協議詳解及常見問題解答

MQTT協議已經成為物聯網數據傳輸的標準,具備以下優勢:

  1. 輕量、高效:IoT裝置上的MQTT實施需要最少的資源,最小的MQTT控制訊息可以少至2個數據位元組,MQTT訊息的頭也很小,可以最佳化網路頻寬。

  2. 可擴充套件:MQTT實施最要最少的程式碼:在操作中消耗的功率非常少。該協議還具有支援與大量物聯網裝置通訊的內建功能。

  3. 可靠:許多IoT裝置透過低頻寬、高延遲的不可靠網路,MQTT具有內建功能,可減少IoT裝置重新連線雲所需時間,還定義了三種不同服務級別,確保IoT用例的可靠性。

  4. 安全:可以輕鬆使用現代身份驗證協議(OAuth、TLS1.3)加密訊息,並對裝置和使用者進行身份認證。

  5. 得到良好的支援:多種語言對MQTT協議的實施,提供廣泛的支援。

2. 協議格式

MQTT報文由三部分組成:

  • Fiexd header固定報頭,所有控制報文都包含

  • Variable header可變報頭,部分控制報文包含

  • Payload有效載荷,部分控制報文包含

在控制報文傳輸中,每一個字串都有一個兩位元組的長度(用了大端序序,高位元組在低位元組前面。高位元組用MSB表示,低位元組用LSB表示)欄位作為字首,這也意味著,控制報文中的字串(注意不是有效載荷裡的內容),長度最大隻能到65535位元組

固定報頭

第一個位元組內容:控制報文型別、控制報文型別標誌位

第二個位元組內容:剩餘長度,表示當前報文剩餘部分的位元組數,包括可變報頭和負載的資料。但是一個位元組只能表示255的長度,所以這裏設計的是可變位元組長度,用來表示不同長度。最大可以傳輸268435422個位元組內容

位元組數 最小值 最大值
1 0(00000000) 127(011111111)
2 128(10000000,00000001) 16383(11111111,01111111)
3 16384(10000000,10000000,00000001) 2097151(11111111,11111111,01111111)
4 2097152(10000000,10000000,10000000,00000001) 268435422(11111111,11111111,11111111,01111111)

3. 控制報文

3.1 CONNECT

客戶端到服務端的網路建立之後,客戶端傳送給服務端的第一個報文必須是Connect報文。在同一個TCP連線上,客戶端只能傳送一次Connect報文。

3.1.1 固定報文

第一個位元組:報文型別為1,無標誌位

3.1.2 可變報文

可變報文由四個欄位,共10位元組組成,按位元組順序:協議名(6位元組)、協議級別(1位元組)、連線標誌(1位元組)、保持連線(2位元組)

協議名:前兩個位元組為協議名稱長度,值為4,後四個位元組為“MQTT”字串。

協議級別:對於MQTT 3.1.1協議,值為4,如果是MQTT5.0協議,值為5。

3.1.2.1 連線標誌

連線標誌位比較多,具體如下所示。

Bit 7 6 5 4 3 2 1 0

User Name Flag Password Flag Will Retain Will QoS Will QoS Will Flag Clean Sesson Reserved
byte 8 X X X X X X X 0

清理會話標誌

Clean Session設定為1,客戶端和服務端必須丟棄之前任何會話,並且開始一個新的會話。

Clean Session設定為0,服務端必須基於當前會話(使用客戶端識別符號識別)的狀態,恢復與客戶端通訊。如果沒有歷史會話,則建立一個新的會話。在連線斷開後,客戶端和服務端必須儲存會話資訊,服務端還要將之後的QoS1和QoS2級別的訊息,儲存為會話狀態的一部分(如果這些訊息匹配斷開連線客戶端的訂閱Topic)。

遺囑標誌

Will Flag被設定為1,表示如果連線請求被接受了,遺囑訊息(Will Message)必須被儲存在服務端,並且與這個客戶端網路關聯。如果這個客戶端網路連線關閉時,服務端必須釋出這個遺囑訊息,除非客戶端在DISCONNECT報文時刪除了遺囑訊息。

Will Flag被設定為1,Will QoS和Will Retain欄位會用到,同時有效載荷中必須包含Will Topic和Will Message。

遺囑QoS標誌

用於指定釋出遺囑訊息時使用的服務質量等級,如果Will Flag為0,Will QoS也必須為0

遺囑保留標誌

Retain訊息=保留訊息

首先理解保留訊息:如果客戶端傳送的訊息中設定了保留標誌,那麼服務端必須儲存這個訊息和對應的服務等級,以便它可以傳送給後來訂閱的客戶端。

一個topic只能有1條保留訊息,新的保留訊息會覆蓋舊的。當傳送保留訊息是,載荷內容為空,會刪除保留訊息。

如果遺囑保留標誌設定為1,服務端必須將遺囑訊息當做保留訊息釋出。也就是說,如果這個客戶端斷開時,會發送遺囑訊息,同時訊息也會保留起來,讓後續新的客戶端定於這個遺囑topic時,也能收到這個遺囑訊息。

使用者名稱標誌

如果使用者名稱標誌被設定為1,那麼有效載荷必須包含使用者名稱欄位

密碼標誌

如果密碼標誌被設定為1,那麼有效載荷必須包含密碼欄位。如果使用者標誌被設定為0,密碼標誌也必須設定為0

3.1.2.2 保持連線

連線保持:以秒為單位的時間間隔,必須要在規定時間內傳送控制報文,如果沒有可以傳送PINGREQ,同時服務端在1.5倍保持連線時間內沒有收到客戶端的控制報文,需要斷開客戶端的網路連線。因為保持連線的只有2位元組儲存,所以理論上最大的就是65535秒,也就是18小時12分。(不過實際應該不會設定這麼長的時間,因為中間路由可能有過期時間)

3.1.3有效載荷

可變報文中的標誌,決定是否包含這些欄位,如果包含,必須按照這個順序出現:客戶端識別符號、遺囑主題,遺囑訊息,使用者名稱,密碼。

客戶端識別符號(ClientId)必須存在,而且必須是Coneect報文的第一個欄位。不過,可以允許客戶端提供一個0位元組的ClientId,服務端需要分配唯一的客戶端識別符號給客戶端。如果客戶端提供了0位元組的clientId,必須同時建清理會話標誌設定為1。(因為會話恢復時根據clientId的)

3.2 PUBLISH

客戶端向服務端,或者服務端向客戶端傳送的一個訊息

3.2.1 固定報頭

第一個位元組:報文型別為3,有三個標誌位:DUP、QoS等級、RETAIN。

重發標誌

DUP設定為0,表示這是客戶端或者服務端第一次傳送這個報文。DUP被設定為1,表示這可能是一個之前傳送過的報文。對於QoS為0的訊息,DUP標誌必須設定為0。

服務質量等級

QoS為0,最多分發一次。

QoS為1,至少分發一次。

QoS為2,只分發一次

保留標誌

如果客戶端傳送的訊息中設定了保留標誌,那麼服務端必須儲存這個訊息和對應的服務等級,以便它可以傳送給後來訂閱的客戶端。

一個topic只能有1條保留訊息,新的保留訊息會覆蓋舊的。當傳送保留訊息時,載荷內容為空,表示刪除保留訊息。

3.2.2可變報頭

主題名:主題名必須是PUBLISH報文中可變報頭的第一個欄位。

報文識別符號:當QoS等級是1或2時,報文識別符號欄位才能出現在PUBLISH報文中。

4. 會話

在物聯網場景中,裝置可能因為網路問題或者電源問題頻繁斷開連線。如果客戶端和服務端總是以全新的上下文建立連線,那麼會有以下問題:

  1. 客戶端在重連後必須重新訂閱主題才能繼續接收訊息,這會給伺服器帶來額外的開銷。

  2. 客戶端將會錯過離線期間的訊息。

  3. QoS1和QoS2的服務質量將無法得到保證。

為此,MQTT協議設計了會話機制。MQTT會話本質上就是,一組需要服務端和客戶端額外儲存的上下文資料,這些資料可以僅持續與網路連線一樣長的時間,也可以跨越多個連續的網路連線存在。

服務端使用ClientID來唯一標識每個會話,如果客戶端想要在連線時複用之前的會話,那麼必須使用與此前一致的ClientID。

在MQTT3.1.1版本中,有Clean Session的會話機制。而在MQTT5.0則升級為Clean Start和Session Expiry Interval更多靈活的機制。

5.QoS

QoS關注的是單個傳送者到單個接受者的應用訊息。

5.1 QoS0:最多分發一次

問題:訊息可能會丟失,因為訊息的可靠性完全依賴底層的TCP協議,而TCP連線出現關閉、重置,就會出現訊息丟失。

訊息的分發依賴底層網路的能力。接受者不會發送訊息響應,傳送者也不會重試。訊息可能送達一次,也可能沒傳送達。

MQTT協議詳解及常見問題解答

5.2 QoS1:至少分發一次

問題:訊息不會丟失,但是可能會重複。

重複的原因:

對於傳送方來說:

  1. PUBLISH未到達接收方

  2. PUBLISH已到達接收方,接受方的PUBACK報文未到達傳送方。

雖然重傳是PUBLISH報文中的DUP標誌被設定為1,用於表示是一個重傳報文,但是接收方無法確認,主要接收方有兩種情況:

  1. 傳送方確實因為沒有收到PUBACK而重傳PUBLISH報文,接收方收到相同PacketID,並且第二個PUBLISH報文DUP為1,確實是重複訊息。

  2. 第一個PUBLISH訊息完成投遞,PacketID可用。傳送方使用這個PacketID傳送全新的PUBLISH報文,但是第一次沒有傳送到對端,又重新發了PUBLISH報文,此時是相同的PacketID,並且DUP為1,確實是個全新的訊息。

接收方無法區分這兩種情況,只能將PUBLISH報文都當作全新的報文處理,因為使用QoS1時,訊息的重複在協議層面無法避免。

QoS1確保訊息至少傳送一次。QoS1的PUBLISH報文的可變報頭中包含一個未使用的報文識別符號(msgId),需要PUBACK確認。

沒有要求要在傳送PUBACK之前,把訊息分發完。當傳送者收到PUBACK時,報文識別符號可以複用。

MQTT協議詳解及常見問題解答

5.3 QoS2:僅分發一次

訊息丟失和重複都不可接受。那麼是如何保證不會重複呢?關鍵在於通訊雙發如何正確地同步釋放PacketID。不論是重傳訊息還是釋出新訊息,一定是和對端達成了共識。

根據QoS2規定:一旦傳送了對應的PUBREL報文後,就不能重發這個PUBLISH報文。收到PUBCOM報文後,這個報文識別符號就可以重用。

因此,對於接收方來說,能夠以PUBREL報文為界限,凡是在PUBREL報文之前到達的PUBLISH報文,都是重複的訊息。凡是在PUBREL之後達到的PUBLISH報文,都是全新的訊息。

QoS2可能的流程一:

MQTT協議詳解及常見問題解答

QoS2可能的流程二:

MQTT協議詳解及常見問題解答

6. Q&A

結合moqueue的實現,進一步理解MQTT協議的內容

  1. 保留訊息處理邏輯?

    Broker會將訊息進行儲存起來,以topic的維度。正常不會清理,除非傳送者傳送了清理保留訊息。

  2. 遺囑訊息處理邏輯

    在ChannelInactive(Netty監聽客戶端斷開),如果有遺囑訊息的話,會發送遺囑訊息邏輯。也就是把遺囑訊息傳送給對應的訂閱者。

  3. 遺囑保留訊息處理邏輯?

    沒有看到處理,只有遺囑訊息的處理,在處理的時候沒有再判斷保留的情況。

  4. 哪些資料需要持久化?會話的資訊?伺服器宕機的話,怎麼處理持久化資訊?

    保留訊息、訂閱訊息。在moquette的實現中,還可能會建立佇列,用來儲存暫時無法寫入的訊息(緩衝區滿了,視窗限流等),會在一些場景(緩衝區可寫,Session會話重開的是)下重新執行。會話是基於記憶體儲存的,伺服器宕機需要重新建立。

  5. MQTT服務端怎麼做負載均衡?

    協議上不支援,需要在MQTT叢集前引入一個負載均衡器,可以是HAProxy 2.4 和 Nginx Plus(Nginx的付費版本)支援。不然只能當成4層轉發進行處理。

  6. QoS1/QoS2如何保障?是收到publish訊息就傳送其他客戶端,還是怎麼樣?

    • QoS1,在broker的實現,會先pulish訊息,然後在回覆ACK。

    • 在QoS2的實現上,會先回復pubrec,然後會publish訊息,偏向於流程二。

    • 實際QoS生效,不僅僅取決於傳送端,還取決於訂閱端。最終到訂閱的QoS為min(傳送端,訂閱端)。實際訊息傳送到訂閱端,就是broker傳送Publish訊息到訂閱段,此時broker是傳送端,訂閱端是接收方。

7. 參考連結

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.