什么是會話?
我們將從客戶端向服務端發起 MQTT 連接請求開始,到連接中斷直到會話過期為止的消息收發序列稱之為會話。因此,會話可能僅持續一個網絡連接,也可能跨越多個網絡連接存在,如果客戶端能在會話過期之前重新建立了連接的話。
在 MQTT v5 中會話過期時間由 Session Expiry Interval 字段決定,早前版本的協議沒有限制會話過期時間,但通常由 MQTT 服務端決定。
什么是會話狀態?
MQTT 要求客戶端與服務端在會話有效期內存儲一系列與客戶端標識相關聯的狀態,稱之為會話狀態。
客戶端需要存儲以下會話狀態:
- 已發送給服務端,但是還沒有完成確認的 QoS 1 與 QoS 2 消息。
- 從服務端收到的,但是還沒有完成確認的 QoS 2 消息。
服務端需要存儲以下會話狀態:
- 會話是否存在,即使會話狀態其余部分為空。
- 客戶端訂閱信息,包括任何訂閱標識符。
- 已發送給客戶端,但是還沒有完成確認的 QoS 1 與 QoS 2 消息。
- 等待傳輸給客戶端的 QoS 0 消息(可選),QoS 1 與 QoS 2 消息。
- 從客戶端收到的,但是還沒有完成確認的 QoS 2 消息,遺囑消息和遺囑延時間隔。
- 會話過期時間。
會話狀態的使用
如果客戶端因為網絡波動等原因導致連接短暫中斷,但在會話過期前重新與服務端建立了連接,那么就可以沿用上次連接建立的訂閱關系,不需要重新訂閱一遍。在低帶寬、不穩定的網絡場景下,網絡中斷可能會發生得很頻繁,保存會話狀態的方式避免了每次連接都需要重新訂閱,降低了重連時客戶端和服務端的資源消耗。服務端在客戶端脫機期間為其保留未完成確認的以及后續到達的消息,客戶端重新連接時再一並轉發,既可以避免消息丟失,也能夠降低某些場景下用戶對網絡變化的感知度。
會話的開始與結束
MQTT v5.0 與 v3.1.1 在會話上有着較為顯著的變化。MQTT v3.1.1 只有一個 Clean Session 字段,由客戶端在連接時指定,為 1 表示客戶端和服務器必須丟棄任何先前的會話並創建一個新的會話,且這個會話的生命周期與網絡連接保持一致;為 0 則表示服務端必須使用與 Client ID 關聯的會話來恢復與客戶端的通信(除非會話不存在),客戶端和服務器在斷開連接后必須存儲會話的狀態。
MQTT v3.1.1 沒有規定持久會話應該在什么時候過期,如果僅從協議層面理解的話,這個持久會話應該永久存在。但在實際場景中這並不現實,因為它會非常占用服務端的資源,所以服務端通常不會遵循協議來實現,而是向用戶提供一個全局配置來限制會話過期時間。
而到了 MQTT 5.0,這個問題得到了妥善的解決,Clean Session 字段被拆分成了 Clean Start 字段與 Session Expiry Interval 字段。Clean Start 字段指定是否需要全新的會話,Session Expiry Interval 字段指定會話過期時間,它們在連接時指定,但 Session Expiry Interval 字段可以在客戶端斷開連接時被更新。因此我們可以很輕易地實現客戶端網絡連接異常斷開時會話被保留,客戶端正常下線時會話則隨着連接關閉而終結的功能。
客戶端如何知道這是被恢復的會話?
顯而易見的是,當客戶端以期望從先前建立的會話恢復狀態的方式發起連接,它需要知道服務端是否存在相應的會話,才能決定在連接建立后是否需要重復一遍訂閱操作。關於這一點,MQTT 協議從 v3.1.1 開始,就為 CONNACK 報文設計了 Session Present 字段,用於表示當前連接使用的是否是一個全新會話,客戶端可以根據這個字段的值進行判斷。
使用建議
開發者需要特別注意 ClientID 與會話之間的聯系,如果某些場景下同一個 ClientID 會被不同的應用或者用戶多次使用,即每次連接都會有完全不同的行為,那么就需要確保每次連接時都請求了全新的會話。合理地評估是否需要持久會話,如非必要可以在正常離線時將會話設置為立即過期減少服務端資源占用。設置合適的會話過期時間,設置過短,可能會失去存儲會話狀態的意義,設置過長,可能會過多地占用服務端資源。
版權聲明: 本文為 EMQ 原創,轉載請注明出處。