遺囑訊息(最後遺囑和遺囑)解釋和範例 | MQTT 5 特點
https://www.emqx.com/en/blog/use-of-mqtt-will-message
目錄
- 什麼是 MQTT 遺囑消息?
- MQTT 訊息如何運作?
- 為什麼遺囑消息還沒公佈?
- 使用遺囑訊息的提示
- 示範
遺囑訊息是MQTT的一個重要特性,它解決了只有伺服器端才能知道客戶端是否離線的問題。它使我們能夠優雅地對意外離線的客戶採取後續行動。
本文將深入探討 MQTT 遺囑訊息,包括它是什麼以及它如何運作。考慮到本文的部分內容會涉及到會話和保留訊息的概念,有需要的可以閱讀以下兩篇部落格:
什麼是 MQTT 遺囑消息?
在現實世界中,一個人可以製定一份遺囑,說明他的資產應該如何分配以及死後應該採取什麼行動。當他去世時,遺囑執行人將遺囑公開並執行遺囑中的指示。
在 MQTT 中,用戶端在連線時可以在伺服器上註冊一條遺囑訊息。與普通訊息類似,我們可以設定遺囑訊息的Topic、Payload等欄位。當客戶端意外斷開連線時,伺服器會將這條遺囑訊息傳送給其他訂閱了相應主題的用戶端。因此,這些接收者可以及時採取行動,例如向使用者發送通知或切換到備份裝置。
假設我們有一個感測器監視一個很少變化的值。典型的實作是定期發布最新值,但更好的實作是僅在值發生變化時以保留訊息的形式發送。這允許任何新訂閱者立即獲取當前值,而無需等待感測器再次發布。
但這也意味著訂閱者無法根據是否按時收到訊息來判斷感測器是否離線。透過Will Message,我們可以立即知道感測器何時保持活動逾時,而不必總是獲取感測器發布的值。
遺囑消息還是遺囑和遺囑 (LWT)?
在一些部落格或程式碼中,我們可能會看到「Last Will and Testament」這個名稱,或其縮寫:LWT。指的是MQTT中的遺囑消息。之所以這兩個名稱並存,可能是因為 MQTT 3.1 協議規範摘要中提到了「Last Will and Testament」的概念。
儘管 MQTT 在協議主體中始終明確使用「Will Message」這個名稱,但這兩個名稱經常在使用者之間互換使用。
我們無意糾正這兩種用法。我們只是希望不同的名稱不會讓您感到困惑。
MQTT 訊息如何運作?
客戶端指定連線時的遺囑訊息
Will Message 是在用戶端發起連線時指定的,它與 Client ID、Clean Start 等欄位一起包含在用戶端傳送的 CONNECT 封包中。
和普通訊息一樣,我們可以為遺囑訊息設定主題(Will Topic)、保留標誌(Will Retain)、屬性(Will Properties)、服務品質(Will QoS)和負載(Will Payload)。
這些欄位的用法與普通訊息中的用法相同。唯一的區別是遺囑訊息的可用屬性與普通應用程式訊息略有不同。下表列出了它們的具體差異:
遺囑訊息總是在客戶「去世」後發布。從某種意義上來說,這也是客戶端發送的最後一則訊息。因此,主題別名在遺囑訊息中沒有任何意義。
此外,遺囑訊息還有一個獨特的屬性:遺囑延遲間隔。這是MQTT 5.0對遺囑訊息引入的一項重要改進,我們將在稍後討論。
連接意外關閉時伺服器發布遺囑訊息
如果用戶端在連線時指定了一條Will Message,那麼伺服器會將該Will Message儲存在對應的會話中,直到滿足下列任一條件才會發佈:
- 伺服器偵測到 I/O 錯誤或網路故障。
- 客戶端在Keep Alive時間內無法通訊。
- 用戶端關閉網路連接,而不先發送原因代碼為 0x00(正常斷開)的 DISCONNECT 封包。
- 伺服器在未先接收到原因代碼為 0x00(正常斷開)的 DISCONNECT 封包的情況下關閉網路連線。例如,由於訊息或行為不符合協定要求,伺服器斷開客戶端的連線。
為了簡單起見,我們可以總結一下,只要網路連線關閉而伺服器沒有收到Reason Code為0x00的DISCONNECT封包,伺服器就需要傳送Will Message。
當客戶端完成工作並主動登出時,可以發送原因碼為0x00的DISCONNECT報文,然後關閉網路連接,以阻止伺服器發布遺囑訊息。
會延遲間隔和延遲發布
預設情況下,當網路連線意外關閉時,伺服器會立即發布遺囑訊息。然而,網路連線中斷通常是暫時的,客戶端可以重新連線並繼續先前的會話。這導致遺囑訊息可能被頻繁且毫無意義地發送。
因此,MQTT 5.0 專門為遺囑訊息添加了一個遺囑延遲間隔屬性,該屬性決定了伺服器在網路連線關閉後延遲多長時間發布遺囑訊息,以秒為單位。
如果遺囑延遲間隔未指定或設定為 0,則伺服器將在網路連線關閉時立即發布遺囑訊息。
如果Will Delay Interval設定為大於0的值,且用戶端在Will Delay Interval到期之前恢復連接,則不會發布Will Message。
遺囑消息和會議
遺囑訊息是伺服器會話狀態的一部分,當會話結束時,遺囑訊息不能繼續單獨存在。
在遺囑訊息延遲發布期間,會話可能會過期,或者伺服器可能需要丟棄先前的會話,因為用戶端在新連線中將 Clean Start 設為 1。
為了避免遺失遺囑訊息,在這種情況下,即使遺囑延遲間隔尚未到期,伺服器也必須發布遺囑訊息。
因此,伺服器最終發布遺囑訊息的時間取決於遺囑延遲間隔到期和會話結束中哪個先發生。
MQTT 3.1.1 中的遺囑消息
在 MQTT 3.1.1 中,只要網路連線關閉而伺服器沒有收到 DISCONNECT 封包,伺服器就應該發布 Will 訊息。
由於 MQTT 3.1.1 沒有遺囑延遲間隔或會話到期間隔,因此當網路連線關閉時,遺囑訊息總是立即發布。
為什麼遺囑消息還沒公佈?
遺囑訊息的延遲發布和取消使得訂閱者是否會收到遺囑訊息的問題變得有些複雜。
我們整理了所有可能的情況,以幫助您更好地理解:
- 如果連線意外關閉且遺囑延遲間隔等於 0,則遺囑訊息將在網路連線關閉時立即發布。
- 如果連線意外關閉且遺囑延遲間隔大於 0,則遺囑訊息將被延遲。最大延遲時間取決於遺囑延遲間隔還是會話到期間隔先到期:
- 如果用戶端在遺囑延遲間隔或會話過期間隔到期之前未能恢復連接,則會發布遺囑訊息。
- 在遺囑延遲間隔或會話到期間隔到期之前:
- 客戶端指定Clean Start為0來恢復連接,不會發布Will Message。
- 用戶端指定 Clean Start 為 1 以恢復連接,由於現有會話結束,因此將立即發布遺囑訊息。
如果現有網路連線尚未關閉,但用戶端使用相同的 Client ID 發起新連接,伺服器將向現有網路連線發送原因碼為 0x8E(Session Taken Over)的 DISCONNECT 封包,然後關閉該連線。這種情況在網路條件較差的情況下很有可能發生,也被認為是意外的連線關閉。
現在考慮這個問題:如果現有網路連線的Session Expiry Interval等於0,Will Delay Interval大於0,當客戶端指定Clean Start為0時,伺服器會傳送Will Message來啟動新的網路連線嗎?
答案是,當現有網路連線中斷時,遺囑訊息將立即發布。
當伺服器關閉現有網路連線時,會話會立即結束,因為會話過期間隔為 0。儘管 Clean Start 設定為 0,但伺服器將為新網路連線建立一個新會話。因此,遺囑訊息將因為會話結束而被發布,滿足場景 2.1 而不是上面列出的 2.2.1。
使用遺囑訊息的提示
使用保留的訊息
一旦伺服器發布遺囑訊息,它將從會話中刪除。如果關心這封遺囑訊息的客戶端不在線,那麼他就會錯過這封遺囑訊息。
為了避免這種情況,我們可以將遺囑訊息設定為保留訊息,這樣遺囑訊息發布後,仍然會以保留訊息的形式儲存在伺服器上,客戶端可以在任何時候。
更進一步,我們也可以對特定客戶端進行狀態監控。
請客戶端 myclient 指定一則 Will 訊息,主題為 myclient/status,負載為離線,每次連線時都會設定 Will Retain 標誌。每當連接成功時,它都會將帶有有效負載的保留訊息在線發佈到主題 myclient/status。然後,我們可以隨時訂閱主題 myclient/status 來取得客戶端 myclient 的最新狀態。
會話到期通知
透過將遺囑延遲間隔設定為大於會話到期間隔,伺服器可以將會話到期通知作為遺囑訊息發送出去。這對於更關心會話過期而不是網路連線中斷的應用程式更有用。即使是主動離線,客戶端也可以發送Reason Code為0x04的DISCONNECT封包,請求伺服器仍然發送Will Message。
示範
使用 MQTTX
安裝並開啟MQTTX ,我們先啟動與Free Public MQTT 代理程式的客戶端連線。就此而言,我們指定了主題為 的遺囑訊息mqttx_c7f95fdf/status
,有效負載為離線,並將遺囑延遲間隔設為 5 秒,會話過期間隔設定為 300 秒。使用Client ID作為主題的前綴可以有效避免與公共伺服器上其他人使用的主題重複:
建立一個新的客戶端連接,連接到公共 MQTT 伺服器,然後訂閱主題 mqttx_c7f95fdf/status 來接收遺囑訊息:
接下來,我們讓第一個客戶端發送一條帶有空主題但設定了主題別名的訊息。由於我們還沒有建立主題和主題別名之間的映射,這會導致伺服器認為客戶端的行為不符合協議規則並關閉連接,然後發送遺囑訊息:
由於設定了遺囑延遲間隔,我們將看到遺囑訊息在發送訊息後 5 秒到達訂閱者:
使用 MQTTX CLI
在終端機中,我們可以使用命令列工具MQTTX CLI來驗證Will Message的行為。接下來,我們看看在發布遺囑訊息之前客戶端連線恢復時會發生什麼。
首先,在第一個終端機視窗中發起連結並訂閱意志主題:
$ client_id= "mqttx_" `日期| sha256sum | sha256sum | 64 位元| 頭-c 8`
$ echo ${client_id}
mqttx_YzFjZmVj
$ mqttx sub -hbroker.emqx.io --topic ${client_id} "/status"
……正在連接……
✔ 已連接
... 正在訂閱 mqttx_YzFjZmVj/status...
✔ 訂閱 mqttx_YzFjZmVj/status
然後,在第二個終端視窗中,建立指定遺囑訊息的用戶端連接,並將遺囑延遲間隔設定為 10 秒,將會話到期間隔設定為 300 秒。
連線成功後輸入Ctrl+C退出,這會導致客戶端不會發送DISCONNECT包,直接中斷網路連線:
$ client_id= "mqttx_YzFjZmVj"
$ mqttx conn -h Broker .emqx .io --client-id ${client_id} --will-topic ${client_id} "/status" --will-message "offline" --will-延遲間隔 10 -- 會話到期間隔 300
……正在連接……
✔ 已連接
^C
執行以下指令,10秒後重新連線:
$ mqttx conn -h 代理.emqx .io --client-id ${client_id} --no-clean --session-expiry-interval 300
第一個終端機視窗中的訂閱者將不會收到遺囑訊息。
這是兩個簡單的例子。您可以使用 MQTTX 和免費 MQTT 公共伺服器來驗證遺囑訊息的更多特徵,例如何時發布遺囑訊息以及何時不發布遺囑訊息。
另外,我們在emqx/MQTT-Features-Example中提供了遺囑訊息的 Python 範例程式碼,您可以參考。
沒有留言:
張貼留言