2023年12月28日 星期四

MQTT練習

 MQTT練習


使用MQTT 協議的設備都運行在網路受限的環境下,而只依靠底層的TCP 傳輸協議,並不能完全保證訊息的可靠到達。因此,MQTT 提供了QoS 機制,其核心是設計了多種訊息互動機制來提供不同的服務質量,來滿足使用者在各種場景下對訊息可靠性的要求。

MQTT 定義了三個QoS 等級,分別為:

  • QoS 0,最多交付一次。
  • QoS 1,至少交貨一次。
  • QoS 2,只交付一次。

其中,

使用QoS 0 可能會遺失訊息,

使用QoS 1 可以保證收到訊息,但訊息可能會重複,

使用QoS 2 可以保證訊息既不遺失也不重複。

QoS 等級從低到高,不僅意味著訊息可靠性的提升,也意味著傳輸複雜程度的提升。

QoS 0 - 最多交付一次

QoS 0 是最低的QoS 等級。QoS 0 訊息即發即棄,不需要等待確認,不需要儲存和重傳,因此對於接收者來說,永遠都不需要擔心收到重複的訊息。

MQTT QoS 0

為什麼QoS 0 訊息會遺失?

當我們使用QoS 0 傳遞訊息時,訊息的可靠性完全依賴底層的TCP 協定。

而TCP 只能保證在連線穩定不關閉的情況下訊息的可靠到達,一旦出現連線關閉、重置,仍有可能遺失目前處於網路連結或作業系統底層緩衝區的訊息。這也是QoS 0 訊息最主要的遺失場景。

QoS 1 - 至少交付一次

為了確保訊息到達,QoS 1 加入了應答與重傳機制,發送方只有在收到接收方的PUBACK 報文以後,才能認為訊息投遞成功,在此之前,發送方需要儲存該PUBLISH 封包以便下次重傳。

QoS 1 需要在PUBLISH 封包中設定Packet ID,而作為回應的PUBACK 封包,則會使用與PUBLISH 封包相同的Packet ID,以便在發送方收到後刪除正確的PUBLISH 封包快取。

MQTT QoS 1

為什麼QoS 1 訊息會重複?

對於發送方來說,沒收到PUBACK 封包分為以下兩種情況:

  1. PUBLISH 未到達接收方
  2. PUBLISH 已到達接收方,接收方的PUBACK 封包尚未到達發送方

在第一種情況下,發送方雖然重傳了PUBLISH 報文,但是對於接收方來說,實際上仍然只收到了一次訊息。

但在第二種情況下,在發送方重傳時,接收方已經收到過了這個PUBLISH 封包,這就導致接收方將收到重複的訊息。

MQTT QoS 1 重複訊息

雖然重傳時PUBLISH 封包中的DUP 標誌會被設定為1,用以表示這是一個重傳的封包。但是接收方並不能因此假定自己曾經接收過這個訊息,仍然需要將其視為一個全新的訊息。

這是因為對於接收方來說,可能存在以下兩種情況:

MQTT QoS 1 重複訊息

第一種情況,發送方因為沒有收到PUBACK 封包而重傳了PUBLISH 封包。此時,接收方收到的前後兩個PUBLISH 封包使用了相同的Packet ID,而第二個PUBLISH 封包的DUP 標誌為1,此時它確實是一個重複的訊息。

第二種情況,第一個PUBLISH 封包已經完成了投遞,1024 這個Packet ID 重新變成可用狀態。發送方使用這個Packet ID 發送了一個全新的PUBLISH 報文,但這次報文未能到達對端,所以發送方後續重傳了這個PUBLISH 報文。這使得雖然接收方收到的第二個PUBLISH 封包同樣是相同的Packet ID,且DUP 為1,但確實是一個全新的訊息。

由於我們無法區分這兩種情況,所以只能讓接收方將這些PUBLISH 封包都當作全新的訊息來處理。因此當我們使用QoS 1 時,訊息的重複在協議層面上是無法避免的。

甚至在比較極端的情況下,例如Br​​oker 從發布方收到了重複的PUBLISH 報文,而在將這些報文轉發給訂閱方的過程中,再次發生重傳,這將導致訂閱方最終收到更多的重複訊息。

在下圖表示的例子中,雖然發布者的本意只是發布一則訊息,但對接收者來說,最終卻收到了三條相同的訊息:

MQTT QoS 1 重複訊息

以上,就是QoS 1 保證訊息到達帶來的副作用。

QoS 2 - 只交付一次

QoS 2 解決了QoS 0、1 訊息可能遺失或重複的問題,但相應地,它也帶來了最複雜的互動流程和最高的開銷。每一次的QoS 2 訊息投遞,都要求發送方與接收方進行至少兩次請求/回應流程。

MQTT QoS 2

  1. 首先,發送方儲存並傳送QoS 為2 的PUBLISH 封包以啟動一次QoS 2 訊息的傳輸,然後等待接收方回覆PUBREC 訊息。這一部分與QoS 1 基本一致,只是回應封包從PUBACK 變成了PUBREC。
  2. 當發送方收到PUBREC 報文,即可確認對端已經收到了PUBLISH 報文,發送方將不再需要重傳這個報文,也不能再重傳這個報文。所以此時發送方可以刪除本地儲存的PUBLISH 封包,然後發送PUBREL 封包,通知對端自己準備將本次使用的Packet ID 標記為可用了。就像PUBLISH 封包一樣,我們需要確保PUBREL 封包到達對端,所以也需要一個回應封包,而這個PUBREL 封包需要被儲存下來以便後續重傳。
  3. 當接收方收到PUBREL 報文,也可以確認在這次的傳輸流程中不會再有重傳的PUBLISH 報文到達,因此回覆PUBCOMP 報文表示自己也準備好將目前的Packet ID 用於新的消息了。
  4. 當發送方收到PUBCOMP 封包,這次的QoS 2 訊息傳輸就算正式完成了。在這之後,發送方可以再次使用目前的Packet ID 發送新的訊息,而接收方再次收到使用這個Packet ID 的PUBLISH 訊息時,也會將它視為一個全新的訊息。

為什麼QoS 2 訊息不會重複?

QoS 2 訊息保證不會遺失的邏輯與QoS 1 相同,所以這裡我們就不再重複了。

與QoS 1 相比,QoS 2 新增了PUBREL 封包和PUBCOMP 封包的流程,也正是這個新增的流程帶來了訊息不會重複的保證。

在我們更進一步之前,我們先快速回顧一下QoS 1 訊息無法避免重複的原因。

當我們使用QoS 1 訊息時,對接收者來說,回復完PUBACK 這個回應封包以後Packet ID 就重新可用了,也不管回應是否確實已經到達了發送者。所以就無法得知之後到達的,攜帶了相同Packet ID 的PUBLISH 報文,到底是發送方因為沒有收到回應而重傳的,還是發送方因為收到了回應所以重新使用了這個Packet ID 發送了一個全新的訊息。

MQTT QoS 1 PUBACK

所以,訊息去重的關鍵就在於,通訊雙方如何正確地同步釋放Packet ID,換句話說,不管發送方是重傳訊息還是發布新訊息,一定是和對端達成共識了的。

而QoS 2 中增加的PUBREL 流程,正是提供了幫助通訊雙方協商Packet ID 何時可以重複使用的能力。

MQTT QoS 2 PUBREL

QoS 2 規定,發送方只有在收到PUBREC 封包之前可以重傳PUBLISH 封包。一旦收到PUBREC 封包並發出PUBREL 封包,發送者就進入了Packet ID 釋放流程,不可以再使用目前Packet ID 重傳PUBLISH 封包。同時,在收到對端回覆的PUBCOMP 封包確認雙方都完成Packet ID 釋放之前,也不可以使用目前Packet ID 傳送新的訊息。

MQTT QoS 2 PUBREC

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

一旦有了這個前提,我們就能夠在協議層面完成QoS 2 訊息的去重。

不同QoS 的適用場景和注意事項

QoS 0

QoS 0 的缺點是可能會遺失訊息,訊息遺失的頻率依賴於你所處的網路環境,並且可能使你錯過斷開連線期間的訊息,不過優點是投遞的效率較高。

所以我們通常選擇使用QoS 0 傳輸一些高頻且不那麼重要的數據,例如感測器數據,週期性更新,即使遺漏幾個週期的數據也可以接受。

QoS 1

QoS 1 可以保證訊息到達,所以適合傳輸一些較為重要的數據,例如下達關鍵指令、更新重要的有即時性要求的狀態等。

但因為QoS 1 也可能會導致訊息重複,所以當我們選擇使用QoS 1 時,還需要能夠處理訊息的重複,或是能夠允許訊息的重複。

在我們決定使用QoS 1 並且不對其進行去重處理之前,我們需要先了解,允許訊息的重複,可能意味著什麼。

如果我們不對QoS 1 進行去重處理,我們可能會遭遇這種情況,發布者以1、2 的順序發布訊息,但最終訂閱方接收到的訊息順序可能是1、2、1、2。如果1 表示開燈指令,2 表示關燈指令,我想大部分使用者都不會接受自己僅僅進行了開燈然後關燈的操作,結果燈在開和關的狀態來回變化。

MQTT QoS

QoS 2

QoS 2 既可以保證訊息到達,也可以保證訊息不會重複,但傳輸成本最高。如果我們不願意自行實現去重方案,並且能夠接受QoS 2 帶來的額外開銷,那麼QoS 2 將是一個合適的選擇。通常我們會在金融、航空等行業場景下會更多地見到QoS 2 的使用。










(1) MQTT    (發行Qos=0)   

MQTT Box 的設定畫面

MQTT Client Name : MQTT test (任意名稱)
Protocol : 選 mqtt/tcp
Host : 輸入 boker.hivemq.com:1883  或 broker.mqtt-dasgboard.com:1883


Topic to publish :  alex9ufo/MQTT/out
Payload : Hello



case1 : MyMQTT 尚未準備 接收先送出 (Publish)

case2 : MyMQTT 準備好接收再送出訊息 (Publish)


MyMQTT (手機App)的設定畫面

Host:  boker.hivemq.com  或 broker.mqtt-dasgboard.com

Port : 1883


訂閱畫面 Subscribe
Topic : alex9ufo/MQTT/out   (大小寫要一致)


MQTT Box 送出後 切換至 Dashboard畫面



(2) MQTT    (發行Qos=1)   



MyMQTT 設定不變 (MyMQTT 暫時不執行)

等MQTTBox送出後再打開APP  MyMQTT



(3) MQTT    (發行Qos=2)   





因為 MyMQTT APP 只能送Qos=0 而已 Qos=1,2 需要付費

沒有留言:

張貼留言

2024_09 作業3 以Node-Red 為主

 2024_09 作業3  (以Node-Red 為主  Arduino 可能需要配合修改 ) Arduino 可能需要修改的部分 1)mqtt broker  2) 主題Topic (發行 接收) 3) WIFI ssid , password const char br...