MQTT協議通俗講解


 

參考 Reference
v3.1.1
其他資源
網站
測試工具
基本概念 Basic Conception
Session 會話
定義
  • 定義:某個客戶端(由ClientID作為標識)和某個服務器之間的邏輯層面的通信
  • 生命周期(存在時間):會話 >= 網絡連接
ClientID
  • 客戶端唯一標識,服務端用於關聯一個Session
  • 只能包含這些 大寫字母,小寫字母 和 數字(0-9a-zA-Z),23個字符以內
  • 如果 ClientID 在多次 TCP連接中保持一致,客戶端和服務器端會保留會話信息(Session)
  • 同一時間內 Server 和同一個 ClientID 只能保持一個 TCP 連接,再次連接會踢掉前一個
CleanSession 標記
  • 在Connect時,由客戶端設置 
  • 0 —— 開啟會話重用機制。網絡斷開重連后,恢復之前的Session信息。需要客戶端和服務器有相關Session持久化機制。
  • 1 —— 關閉會話重用機制。每次Connect都是一個新Session,會話僅持續和網絡連接同樣長的時間。
客戶端 Session
  • 已經發送給服務端,但是還沒有完成確認的 QoS 1 和 QoS 2 級別的消息 
  • 已從服務端接收,但是還沒有完成確認的 QoS 2 級別的消息
服務器端 Session
  • 會話是否存在,即使會話狀態的其它部分都是空  (SessionFlag)
  • 客戶端的訂閱信息  (ClientSubcription)
  • 已經發送給客戶端,但是還沒有完成確認的 QoS 1 和 QoS 2 級別的消息
  • 即將傳輸給客戶端的 QoS 1 和 QoS 2 級別的消息
  • 已從客戶端接收,但是還沒有完成確認的 QoS 2 級別的消息
  • (可選)准備發送給客戶端的 QoS 0 級別的消息
長連接維護與管理
Keep Alive 心跳
  • 目的是保持長連接的可靠性,以及雙方對彼此是否在線的確認。
  • 客戶端在Connect的時候設置 Keep Alive 時長。如果服務端在 1.5 * KeepAlive 時間內沒有收到客戶端的報文,它必須斷開客戶端的網絡連接
  • Keep Alive 的值由具體應用指定,一般是幾分鍾。允許的最大值是 18 小時 12 分 15 秒
Will 遺囑
  • 遺囑消息(Will Message)存儲在服務端,當網絡連接關閉時,服務端必須發布這個遺囑消息,所以被形象地稱之為遺囑,可用於通知異常斷線。
  • 客戶端發送 DISCONNECT 關閉鏈接,遺囑失效並刪除
  • 遺囑消息發布的條件,包括: 
  • 服務端檢測到了一個 I/O 錯誤或者網絡故障
  • 客戶端在保持連接(Keep Alive)的時間內未能通訊
  • 客戶端沒有先發送 DISCONNECT 報文直接關閉了網絡連接
  • 由於協議錯誤服務端關閉了網絡連接
  • 相關設置項,需要在Connect時,由客戶端指定
  • Will Flag —— 遺囑的總開關
  • 0 -- 關閉遺囑功能,Will QoS 和 Will Retain 必須為 0
  • 1 --  開啟遺囑功能,需要設置 Will Retain 和 Will QoS
  • Will QoS —— 遺囑消息 QoS
  • 可取值 0、1、2,含義與消息QoS相同
  • Will Retain —— 遺囑是否保留
  • 0 -- 遺囑消息不保留,后面再訂閱不會收到消息
  • 1 -- 遺囑消息保留,持久存儲
  • Will Topic —— 遺囑話題
  • Will Payload —— 遺囑消息內容
消息基本概念
報文標識 Packet Identifier                                                     
  • 存在報文的可變報頭部分,非零兩個字節整數 (0-65535]
  • 一個流程中重復:這些報文包含 PacketID,而且在一次通信流程內保持一致:
  • PUBLISH(QoS>0 時)PUBACKPUBRECPUBRELPUBCOMP
  • SUBSCRIBE,  SUBACK
  • UNSUBSCIBEUNSUBACK                                        
  • 新的不重復:客戶端每次發送一個新的這些類型的報文時都必須分配一個當前 未使用的PacketID
  • 當客戶端處理完這個報文對應的確認后,這個報文標識符就釋放可重用。
  • 獨立維護:客戶端和服務端彼此獨立地分配報文標識符。因此,客戶端服務端組合使用相同的報文標識符可以實現 並發 的消息交換。可能出現一下情況,並不算異常:
Payload 有效載荷,消息體
  • 最大允許 256MB
  • Publish 的 Payload 允許為空。在很多場合下,代表將持久消息(或者遺囑消息)清空。
  • UTF-8編碼
Retain 持久消息(粘性消息)
  • RETAIN 標記:每個Publish消息都需要指定的標記
  • 0 —— 服務端不能存儲這個消息也不能移除或替換任何 現存的保留消息               
  • 1 —— 服務端必須存儲這個應用消息和它的QoS等級,以便它可以被分發給未來的訂閱者 
  • 每個Topic只會保留最多一個 Retain 持久消息
  • 客戶端訂閱帶有持久消息的Topic,會立即受到這條消息
  • 服務器可以選擇丟棄持久消息,比如內存或者存儲吃緊的時候
  • 如果客戶端想要刪除某個Topic 上面的持久消息,可以向這個Topic發送一個Payload為空的持久消息
  • 遺囑消息(Will)的Retain持久機制同理
QoS 服務等級(消息可靠性)
最多一次 At most Once(QoS == 0)
  • 沒有回復,不需要存儲。有可能丟失(網絡異常斷開,業務層繁忙或者錯誤)
至少一次 At least Once(QoS == 1 
  • 發送者S 發送前需要做持久化存儲,接受者R 不需要持久化存儲
  • 如果 發送者S 沒有收到 接收者R 的回復 PUBACK,過一段時間 發送者S 會重新發送,DUP標記為1(在同一Session內)。
  • 接受者R 發送 PUBACK 后,不需要知道對方是否收到,馬上把消息交給上層業務。如果此時網絡異常,會導致發送者重發。這樣接受者收到多個消息(所以叫至少一次)。
有且僅有一次 Exactly Once(QoS == 2 )
  • 發送者S 發送 PUBLISH 前,需要做持久化存儲。接受者R 回復PUBREC 后,也需要做持久化存儲
  • 如果 發送者S 沒有收到 接收者R 的回復 PUBREC,過一段時間 發送者S 會重新發送,DUP標記為1(在同一Session內)。
  • 如果 接受者R 沒有收到 發送者S 的回復 PUBREL,過一段時間 接受者R 會重新發送PUBREC。
  • 發送者S 收到 PUBREC后,刪除持久化消息,但是要保存 PacketID
  • 接收者R 受到 PUBREL后,刪除持久化PUBREC。然后將消息發給上層,同時回復 PUBCOMP。
  • 發送者S 收到 PUBCOMP 后,刪除 PacketID,通信完美結束。
  • 這套流程可以 嚴格保證 一個包不管在什么情況下 接收者R 只收到一次 。
重傳標記 DUP 與重傳機制 (QoS > 0)
  • 如果客戶端或者服務器發送了一個 Publish 消息,一段時間內沒收到 PublishAck 回復,則認為消息丟失,進行重傳。
  • 在一個Session內,進行重傳的時候,頭部的 DUP 重傳標志 設置為1。
  • 客戶端有可能收到 DUP == 0 的重傳包(Payload相同,PacketID不同)。因為可能因為網絡問題,下次重傳時間較久,Session已經釋放,PacketID 已經變更。
  • 客戶端發給服務器的和服務器轉發給別的客戶端的 Publish 消息,DUP 重傳標志不會傳遞
  • 接收者收到一個 DUP 標志為 1 的控制報文時,並不能保證之前收到過相同的報文
消息重傳順序                                    
  • 重發任何之前的 PUBLISH 報文時,必須按原始 PUBLISH 報文的發送順序重發 (適用於QoS 1 和 QoS 2 消息)
  • 必須按照對應的 PUBLISH 報文的順序發送 PUBACK 報文 (QoS 1 消息)
  • 必須按照對應的 PUBLISH 報文的順序發送 PUBREC 報文 (QoS 2 消息)
  • 必須按照對應的 PUBREC 報文的順序發送 PUBREL 報文 (QoS 2 消息)
  • QoS == 1 時,雖然是PUBLISH有序的,但是可能會重復。例如,發布者按順序 1,2,3,4 發送消息,訂閱者收到的順序可能是 1,2,3,2,3,4。 
  • QoS == 1 時,如果限制 傳輸窗口 (in-flight window==1,即同一時刻只有一個包在傳輸,就可以保證亂序。例如,訂閱者收到的順序可能是 1,2,3,3,4,而不是 1,2,3,2,3,4 
  • QoS == 2 時,肯定不會存在亂序的問題。
 
話題 與訂閱機制  Topic & Subcribe
Topic 話題 和 TopicFilter 話題過濾器
  • Pub-Sub消息模型的核心機制
  • UTF-8 編碼字符串,不能超過 65535 字節。層級數量沒有限制
  • 不能包含任何的下文中提到的特殊符號(/、+、#),必須至少包含一個字符  
  • 區分大小寫,可以包含空格不能包含空字符 (Unicode U+0000)  
  • 在收部或尾部增加 斜杠 “/”,會產生不同的Topic和TopicFilter。舉例:
  • “/A” 和 A” 是不同的
  • “A” 和 “A/” 是不同的
  • 只包含斜杠 “/”  Topic  TopicFilter 是合法的                                                           
TopicFilter中的特殊符號
  • 層級分隔符 /
  • 用於分割主題的每個層級,為主題名提供一個分層結構       
  • 主題層級分隔符可以出現在 Topic 或 TopicFilter 的任何位置                           
  • 特例:相鄰的主題層次分隔符表示一個零長度的主題層級         
  • 單層通配符 +
  • 只能用於單個主題層級匹配的通配符。例如,“a/b/+” 匹配 “a/b/c1” 和 “a/b/c2” ,但是不匹配 “a/b/c/d”      
  • 可以匹配 任意層級,包括第一個和最后一個層級。例如,“+” 是有效的“sport/+/player1” 也是有效的。
  • 可以在多個層級中使用它,也可以和多層通配符一起使用。 例如,“+/tennis/#” 是有效的。                
  • 只能匹配本級不能匹配上級。例如,“sport/+” 不匹配 “sport” 但是卻匹配“sport/”“/finance” 匹配 “+/+” 和 “/+” ,但是不匹配 “+”。 
  • 多層通配符 #
  • 用於匹配主題中任意層級的通配符
  • 匹配包含本身的層級和子層級。例如 a/b/c/#" 可以匹配 “a/b/c”、a/b/c/d 和  a/b/c/d/e
  • 必須是最后的結尾。例如“sport/tennis/#/ranking”是無效的      
  • “#”是有效的,會收到所有的應用消息。 (服務器端應將此類 TopicFilter禁掉 )
$開頭的,服務器保留
  • 服務端不能將 $ 字符開頭的 Topic 匹配通配符 (#或+) 開頭的 TopicFilter
  • 服務端應該阻止客戶端使用這種 Topic 與其它客戶端交換消息。服務端實現可以將 $ 開頭的主題名用作其他目的。
  • $SYS/ 被廣泛用作包含服務器特定信息或控制接口的主題的前綴
  • 客戶端不特意訂閱 $開頭的 Topic,就不會收到對應的消息
  • 訂閱 “#” 的客戶端不會收到任何發布到以 “$” 開頭主題的消息
  • 訂閱 “+/A/B” 的客戶端不會收到任何發布到 “$SYS/A/B” 的消息
  • 訂閱 “$SYS/#” 的客戶端會收到發布到以 “$SYS/” 開頭主題的消息
  • 訂閱 “$SYS/A/+” 的客戶端會收到發布到 “$SYS/A/B” 主題的消息
  • 如果客戶端想同時接受以 “$SYS/” 開頭主題的消息和不以 $ 開頭主題的消息,它需要同時 訂閱 “#” 和 “$SYS/#”
訂閱 Subscribe  與 QoS降級
  • 訂閱機制基於TopicFilter匹配
  • 一個Subsribe請求 可訂閱多個 Topic(節省帶寬,多訂閱盡量用一次請求)。取消訂閱也同理
  • 每一個訂閱需要指定一個QoS,指定了客戶端接收消息所允許的最大QoS級別。但是服務器端最終授權返回的QoS可能會小於等於客戶端請求的QoS
  • 對於高於QoS的消息(比如說訂閱的QoS限制到1,消息的QoS指定到2),那么客戶端會收到一個QoS降低為指定的 限制QoS 的消息(消息的QoS降為1,不保證只收到一次)
  • 訂閱關系可以被覆蓋,以TopicFilter為標識。如果后面訂閱一個相同的TopicFilter,但是指定的QoS不同,則以后面的為准,QoS升高后,重發相應等級的 Retain 消息
安全傳輸與鑒權認證  Security & Certification
傳輸層
  • 可以采用 TCP、SSL/TLS [RFC5246WebSocket 作為傳輸層。UDP不可以,因為不保證可靠傳輸與有序傳輸。
  • 服務器端返回的數據極有可能出現 粘包 的情況。客戶端經常會在連接建立之后,連續調用多個訂閱,這樣服務器端就會回復多個訂閱ACK包,同時還有各個Topic上的持久消息,一般粘成一個TCP包返回過來
  • 端口(IANA分發)
  • 1883:over TCP,無加密
  • 8883:over SSL/TLS,單向認證(強烈建議)
  • 8884:over SSL/TLS,雙向認證
  • 8080:over WebSockets,未加密
  • 8081:over WebSockets,加密                                                  
  • 可使用SOCKS代理,可利用安全隧道(如SSH)
潛在的風險與應對機制
  • 潛在風險
  • 設備可能會被盜用
  • 客戶端和服務端的靜態數據可以被訪問(比如客戶端Root導致數據泄露、服務器被拖庫)
  • 協議規定的行為可能有副作用 (如計時器攻擊  “timing attacks”)
  • 拒絕服務攻擊(DoS)
  • 通信可能會被攔截、修改、重定向或者泄露(抓包、中間人)
  • 虛假控制報文注入
  • 應對的機制
  • 用戶和設備身份認證
  • 服務端資源訪問授權
  • 控制報文和 Payload 的完整性校驗
  • 控制報文和 Payload 的隱私控制
客戶端身份驗證與授權  (Authentication & Authorization of Client
  • 用戶名+密碼驗證:Connect 登錄的時候,傳入 UserName 和 Password
  • 相關標記位:在Connect時,由客戶端設置
  • 用戶名(UserName Flag)標記設置為1,才可以穿入
  • 密碼(Password Flag)標記設置為1
  • 外部驗證:LDAP、OAuth 或者 操作系統的認證機制
  • 用戶名密碼加密:防止中間人攻擊和重放攻擊
  • 應用層:客戶端通過應用消息給服務端發送憑證用於身份驗證。
  • 授權:基於客戶端提供的信息如用戶名、客戶端標識符(ClientId)、客戶端的主機名或 IP 地址,或者身份認證的結果,服務端可以限制對某些服務端資源的訪問
服務端身份驗證 (Authentication of Server by Client
  • MQTT 協議不是雙向信任的,它沒有提供客戶端驗證服務端身份的機制
  • TLS:客戶端可以使用服務端發送的SSL證書驗證服務端的身份
  • 應用層:可以通過服務端給客戶端發送憑證用於身份驗證的應用層消息
  • VPN:在客戶端和服務端之間使用虛擬專用網(VPN)可以確保客戶端連接的是預期的服務器。
控制報文和 Payload 的完整性(Integrity)
  • TLS:提供了對網絡傳輸的數據做完整性校驗的哈希算法
  • 應用層:可以在應用消息中單獨包含哈希值。這樣做可以為 PUBLISH 控制報文的網絡傳輸和靜態數據提供內容的完整性檢查
  • VPN:在客戶端和服務端之間使用虛擬專用網(VPN)連接可以在 VPN 覆蓋的網絡段提供數據完整性檢查
控制報文和 Payload 的保密性(Privacy)
  • 輕量級加密:AES or DES,可適用於低端設備
  • TLS:可以對網絡傳輸的數據加密
  • 應用層:可以單獨加密 Payload 內容。這可以提供 Payload 傳輸途中和靜態數據的私密性。但不能給應用消息的其它屬性如 Topic 加密
  • 靜態數據加密:客戶端和服務端實現可以加密存儲靜態數據,例如可以將應用消息作為會話的一部分存儲
  • VPN:在客戶端和服務端之間使用虛擬專用網(VPN)連接可以在 VPN 覆蓋的網絡段保證數據的私密性
異常行為的檢測
  • 服務端實現可以監視客戶端的行為,檢測潛在的安全風險。例如:
  • 重復的連接請求
  • 重復的身份驗證請求
  • 連接的異常終止
  • 主題掃描 (請求發送或訂閱大量主題)
  • 發送無法送達的消息 (沒有訂閱者的主題)   
  • 客戶端連接但是不發送數據
  • 應對策略
  • 發現違反安全規則的行為,服務端實現可以斷開客戶端連接
  • 可以基於 IP地址 或 ClientID 實現一個 動態黑名單列表
  • 可以使用網絡層面的控制,實現基於 IP 地址或其它信息的 速率限制 或黑名單
  • 連接拒絕與錯誤碼
 
 
最佳實踐  Best Practice
客戶端 Client
選型 
  • Android
  • iOS
  • Javascript
  • PHP
  • Java
服務器端 Server
Broker選型
  • Mosca
Benchmark
分布式部署 Cluster


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM