物聯網之MQTT3.1.1和MQTT5協議 (17) 操作行爲

前言

本文介紹了PUBACK報文,PUBREC 報文,PUBREL報文,PUBCOMP報文, AUTHB報文。並且還會介紹關於流控,錯誤處理,MQTT的訂閱匹配語法和響應的QoS流程。

操作行爲

狀態存儲(MQTT3.1.1)

爲了提供服務質量保證,客戶端和服務端有必要存儲會話狀態。在整個會話期間,客戶端和服務端都必須存儲會話狀態 。會話必須至少持續和它的活躍網絡連接同樣長的時間。

服務端的保留消息不是會話狀態的組成部分。服務端應該保留那種消息直到客戶端刪除它。

客戶端和服務端實現的存儲容量必然是有限的,還可能要受管理策略的限制,比如跨網絡連接的會話狀態的最大存儲時間。已保存的會話狀態丟失可能是某個管理操作造成的,例如對某個預定義條件的自動響應。它造成的後果就是會話終止。這些操作可能是資源限制或其他操作原因引發的。需要謹慎的評估客戶端和服務端的存儲容量,以確保存儲空間夠用。

客戶端或服務端的軟硬件故障都可能導致會話狀態的丟失或損壞。

服務器和客戶端操作正常可能意味着,已保存的狀態丟失或損壞是管理操作或軟硬件故障造成的。管理操作可能是對某個預定義條件的自動響應。這些操作可能是資源限制或其他操作原因引發的。例如,服務端可能會基於外部條件,決定不再將某個消息或某些消息分發給任何當前的或以後的客戶端。

MQTT用戶應該評估MQTT客戶端和服務端實現的存儲容量,確保能滿足需求。

會話狀態(MQTT 5)

爲實現QoS等級1和QoS等級2協議流,客戶端和服務端需要將狀態與客戶標識符相關聯,這被稱爲會話狀態。服務端還將訂閱信息存儲爲會話狀態的一部分。

會話可以跨越一系列的網絡連接。它持續到最新的網絡連接(Network Connections)加上會話過期間隔(Session Expiry Interval)。

客戶端的會話狀態包括:

  • 已發送給服務端,但是還沒有完成確認的QoS等級1和QoS等級2的消息
  • 從服務端收到的,但是還沒有完成確認的QoS等級2消息

服務端的會話狀態包括:

  • 會話是否存在,即使會話狀態其餘部分爲空
  • 客戶端訂閱信息,包括任何訂閱標識符
  • 已發送給客戶端,但是還沒有完成確認的QoS等級1和QoS等級2的消息
  • 等待發送到客戶端的QoS 1和QoS 2消息,等待發送到客戶端的可選QoS 0消息。
  • 已從客戶端收到但尚未完全確認的QoS 2消息.Will消息和Will Delay間隔
  • 如果會話當前未連接,會話結束時間和會話狀態將被丟棄

保留消息不是會話狀態的一部分,會話結束時不被刪除。

存儲會話狀態

當網絡連接打開時,客戶端和服務端不能丟棄會話狀態。當網絡連接被關閉並且會話過期間隔已過時,服務端必須丟棄會話狀態。

和MQTT 3.1.1在狀態存儲一節描述相差無幾,但多了一個判斷,即會話過期間隔時間。

非規範示例

例如,想要收集電錶讀數的用戶可能會決定使用QoS 1等級的消息,因爲他們不能接受數據在網絡傳輸途中丟失,但是,他們可能認爲客戶端和服務端的數據可以存儲在內存(易失性存儲器)中,因爲(他們覺得)電力供應是非常可靠的,不會有太大的數據丟失風險。

與之相反,停車計費支付應用的提供商可能決定任何情況下都不能讓數據支付消息丟失,因此他們要求在通過網絡傳輸之前,所有的數據必須寫入到非易失性存儲器中(如硬盤)。

網絡連接

MQTT協議要求基礎傳輸層能夠提供有序的、可靠的、雙向傳輸(從客戶端到服務端和從服務端到客戶端)字節流。此規範不要求任何指定的傳輸協議。客戶端或服務端可以支持這裏列出的任何傳輸協議,或者滿足本節要求的任何其他傳輸協議。

MQTT v5.0,和MQTT v3.1.1使用的傳輸層協議是 [RFC0793] 定義的TCP/IP協議。下面的協議也支持:

TCP端口8883和1883已在IANA註冊,分別用於MQTT的TLS和非TLS通信。

無連接的網絡傳輸,如用戶數據包協議 (UDP) 本身不適合,因爲它們可能丟失或重新排列數據。

服務質量等級和協議流程

MQTT按照這裏定義的服務質量 (QoS) 等級分發應用消息。分發協議是對稱的,在下面的描述中,客戶端和服務端既可以是發送者也可以是接收者。分發協議關注的是從單個發送者到單個接收者的應用消息。服務端分發應用消息給多個客戶端時,每個客戶端獨立處理。分發給客戶端的出站應用消息和入站應用消息的QoS等級可能是不同的。
下面的非規範流程圖展示了可能的實現方法。

QoS 0:最多分發一次

消息的分發依賴於底層網絡的能力。接收者不會發送響應,發送者也不會重試。消息可能送達一次也可能根本沒送達。

對於QoS 0的分發協議,發送者

  • 必須發送QoS等於0,DUP等於0的PUBLISH報文

對於QoS 0的分發協議,接收者

  • 接受PUBLISH報文時同時接受消息的所有權
QoS 0協議流程圖,非規範示例

在這裏插入圖片描述

QoS 1: 至少分發一次

服務質量確保消息至少送達一次。QoS 1的PUBLISH報文的可變報頭中包含一個報文標識符,需要PUBACK報文確認。

對於QoS 1的分發協議,發送者

  • 每次發送新的應用消息都必須分配一個未使用的報文標識符。
  • 發送的PUBLISH報文必須包含報文標識符且QoS等於1,DUP等於0。
  • 必須將這個PUBLISH報文看作是 未確認的 ,直到從接收者那收到對應的PUBACK報文。

一旦發送端收到PUBACK報文,這個報文標識符就可以重用。

注意:允許發送端在等待確認時使用不同的報文標識符發送後續的PUBLISH報文。

對於QoS 1的分發協議,接收者

  • 響應的PUBACK報文必須包含一個報文標識符,這個標識符來自接收到的、已經接受所有權的PUBLISH報文。
  • 發送了PUBACK報文之後,接收者必須將任何包含相同報文標識符的入站PUBLISH報文當作一個新的消息,並忽略它的DUP標誌的值。
    在這裏插入圖片描述

不要求接收者在發送PUBACK之前完整分發應用消息。原來的發送者收到PUBACK報文之後,應用消息的所有權就會轉移給這個接收者。

QoS 2:僅分發一次

這是最高等級的服務質量,消息丟失和重複都是不可接受的。使用這個服務質量等級會有額外的開銷。

QoS 2的消息可變報頭中有報文標識符。QoS 2的PUBLISH報文的接收者使用一個兩步確認過程來確認收到。

對於QoS 2的分發協議,發送者

  • 必須給要發送的新應用消息分配一個未使用的報文標識符。

  • 發送的PUBLISH報文必須包含報文標識符且報文的QoS等於2,DUP等於0。

  • 必須將這個PUBLISH報文看作是 未確認的 ,直到從接收者那收到對應的PUBREC報文

  • 收到PUBREC報文後必須發送一個PUBREL報文。PUBREL報文必須包含與原始PUBLISH報文相同的報文標識符

    MQTT5額外要求是原因碼小於0x80的PUBREC報文

  • 必須將這個PUBREL報文看作是 未確認的 ,直到從接收者那收到對應的PUBCOMP報文。

  • 一旦發送了對應的PUBREL報文就不能重發這個PUBLISH報文。

  • 【MQTT5】如果PUBLISH報文已發送,不能應用消息過期屬性

一旦發送者收到PUBCOMP報文,這個報文標識符就可以重用。

MQTT5要求是包含原因碼大於0x80的PUBCOMP報文

注意:允許發送者在等待確認時使用不同的報文標識符發送後續的PUBLISH報文。

對於QoS 2的分發協議,接收者:

  • 響應的PUBREC報文必須包含報文標識符,這個標識符來自接收到的、已經接受所有權的PUBLISH報文

  • 【MQTT5】如果接收端發送了包含原因碼大於等於0x80的PUBREC報文,它必須將後續包含相同報文標識符的PUBLISH報文當做是新的應用消息

  • 在收到對應的PUBREL報文之前,接收者必須發送PUBREC報文確認任何後續的具有相同標識符的PUBLISH報文。 在這種情況下,它不能重複分發消息給任何後續的接收者。

  • 響應PUBREL報文的PUBCOMP報文必須包含與PUBREL報文相同的標識符

  • 發送PUBCOMP報文之後,接收者必須將包含相同報文標識符的任何後續PUBLISH報文當作一個新的publication。

    MQTT5認爲是新的應用消息

  • 即使已應用消息到期,也必須繼續QoS 2確認序列

在這裏插入圖片描述

不要求接收者在發送PUBREC或PUBCOMP之前完整分發應用消息。原來的發送者收到PUBREC報文之後,應用消息的所有權就會轉移給這個接收者。

【MQTT5】

然而,接收端需要在接受所有權之前執行對所有可能導致轉發失敗(例如超出配額、權限等)的條件的檢查。接收端在PUBREC中使用適當的原因碼指示所有權接受成功或失敗。

消息分發重試

【MQTT3.1.1】

客戶端設置清理會話(CleanSession)標誌爲0重連時,客戶端和服務端必須使用原始的報文標識符重發任何未確認的PUBLISH報文(如果QoS>0)和PUBREL報文。這是唯一要求客戶端或服務端重發消息的情況。

控制報文的重發曾經需要克服某些陳舊TCP網絡上的數據丟失問題。部署在那些環境中的MQTT 3.1.1實現可能仍然需要關注這個問題。

【MQTT5】

客戶端以新開始(Clean Start)標誌爲0且會話存在的情況下重連時,客戶端和服務端都必須使用原始報文標識符重新發送任何未被確認的PUBLISH報文(當QoS > 0)和PUBREL報文。這是唯一要求客戶端或服務端重發消息的情況。客戶端和服務端不能在其他任何時間重發消息

如果收到包含原因碼大於等於0x80的PUBACK或PUBREC,則對應的PUBLISH報文被看作已確認,且不能被重傳

消息收到

服務端接管入站應用消息的所有權時,它必須將消息添加到訂閱匹配的客戶端的會話狀態中。

通常情況下,客戶會收到響應其創建的訂閱的消息。 客戶端還可能收到與任何其明確訂閱都不匹配的消息。 如果服務器自動爲客戶端分配了訂閱,則會發生這種情況。 客戶端還可以在進行UNSUBSCRIBE操作時接收消息。 客戶端必須根據適用的QoS規則確認收到的任何發佈數據報文,無論其是否選擇處理其包含的應用消息。

消息排序

實現定義的協議流程時,客戶端必須遵循下列規則:

  • 重發任何之前的PUBLISH報文時,必須按原始PUBLISH報文的發送順序重發(適用於QoS 1和QoS 2消息)
  • 必須按照對應的PUBLISH報文的順序發送PUBACK報文(QoS 1消息)
  • 必須按照對應的PUBLISH報文的順序發送PUBREC報文(QoS 2消息)
  • 必須按照對應的PUBREC報文的順序發送PUBREL報文(QoS 2消息)

默認情況下,服務端轉發訂閱的消息【在MQTT中認爲是非共享訂閱的消息】時,必須將每個主題都視爲有序主題。它可以提供一個管理功能或其它機制,以允許將一個或多個主題當作是無序的。

【MQTT5】

一個有序主題(Ordered Topic)是一個主題,在這個主題中,客戶端可以確定從同一個客戶端接收的相同QoS等級的消息的順序與他們發佈的順序一致。當服務端處理髮布到有序主題的消息時,它必須按照消息從任何給定客戶端接收的順序發送PUBLISH報文給消費端(對於同一主題和QoS等級) 。這是上面列出的規則的補充。

上面列出的規則確保,使用QoS 1發佈和訂閱的消息流,訂閱者按照消息發佈時的順序收到每條消息的最終副本,但是消息可能會重複,這可能導致在它的後繼消息之後收到某個已經收到消息的重發版本。例如,發佈者按順序1,2,3,4發送消息,訂閱者收到的順序可能是1,2,3,2,3,4。

如果客戶端和服務端能保證任何時刻最多有一條消息在 傳輸中(in-flight)(在某條消息被確認前不發送後面的那條消息),那麼,不會有QoS 1的消息會在它的任何後續消息之後收到。 例如,訂閱者收到的順序可能是1,2,3,3,4,而不是1,2,3,2,3,4。【MQTT3.1.1】將傳輸窗口 (in-flight window)設爲1意味着,在同一個主題上,即使發佈者發送了一系列不同QoS等級的消息,它們的順序也被保留。

主題名和主題過濾器

主題通配符

主題層級(topic level)分隔符用於將結構化引入主題名。如果存在分隔符,它將主題名分割爲多個主題層級 topic level 。

訂閱的主題過濾器可以包含特殊的通配符,允許客戶端一次訂閱多個主題。

主題過濾器中可以使用通配符,但是主題名不能使用通配符。

主題層級分隔符

斜槓('/' U+002F)用於分割主題的每個層級,爲主題名提供一個分層結構。當客戶端訂閱指定的主題過濾器包含兩種通配符時,主題層級分隔符就很有用了。主題層級分隔符可以出現在主題過濾器或主題名字的任何位置。相鄰的主題層次分隔符表示一個零長度的主題層級。

多層通配符

數字標誌('#' U+0023)是用於匹配主題中任意層級的通配符。多層通配符表示它的父級和任意數量的子層級。多層通配符必須位於它自己的層級或者跟在主題層級分隔符後面。不管哪種情況,它都必須是主題過濾器的最後一個字符。

例如,如果客戶端訂閱主題 sport/tennis/player1/#,它會收到使用下列主題名發佈的消息:

  • sport/tennis/player1
  • sport/tennis/player1/ranking
  • sport/tennis/player1/score/wimbledon
  • sport/# 也匹配單獨的 sport因爲 # 包括它的父級。
  • # 是有效的,會收到所有的應用消息
  • sport/tennis/# 也是有效的。
  • sport/tennis# 是無效的。
  • sport/tennis/#/ranking 是無效的。
單層通配符

加號 ('+' U+002B) 是隻能用於單個主題層級匹配的通配符。

在主題過濾器的任意層級都可以使用單層通配符,包括第一個和最後一個層級。然而它必須佔據過濾器的整個層級。可以在主題過濾器中的多個層級中使用它,也可以和多層通配符一起使用。

例如, sport/tennis/+ 匹配 sport/tennis/player1sport/tennis/player2 ,但是不匹配 sport/tennis/player1/ranking 。同時,由於單層通配符只能匹配一個層級, sport/+ 不匹配 sport 但是卻匹配 sport/

  • + 是有效的。
  • +/tennis/# 是有效的。
  • sport+ 是無效的。
  • sport/+/player1 也是有效的。
  • /finance 匹配 +/+/+ ,但是不匹配 +

以$開頭的主題

服務端不能將 $ 字符開頭的主題名匹配通配符(#+)開頭的主題過濾器。服務端應該阻止客戶端使用這種主題名與其它客戶端交換消息。服務端實現可以將 $開頭的主題名用作其他目的。

  • $SYS/ 被廣泛用作包含服務器特定信息或控制接口的主題的前綴。
  • 應用不能使用 $ 字符開頭的主題。
  • 訂閱 # 的客戶端不會收到任何發佈到以 $ 開頭主題的消息。
  • 訂閱 +/monitor/Clients 的客戶端不會收到任何發佈到 $SYS/monitor/Clients 的消息。
  • 訂閱 $SYS/# 的客戶端會收到發佈到以$SYS/ 開頭主題的消息。
  • 訂閱 $SYS/monitor/+的客戶端會收到發佈到 $SYS/monitor/Clients 主題的消息。
  • 如果客戶端想同時接受以 $SYS/開頭主題的消息和不以$ 開頭主題的消息,它需要同時訂閱 #$SYS/#

主題語義和用法

主題名和主題過濾器必須符合下列規則:

  • 所有的主題名和主題過濾器必須至少包含一個字符
  • 主題名和主題過濾器是區分大小寫的
  • 主題名和主題過濾器可以包含空格
  • 主題名或主題過濾器以前置或後置斜槓 /區分。
  • 只包含斜槓 /的主題名或主題過濾器是合法的。
  • 主題名和主題過濾器不能包含空字符 (Unicode U+0000)
  • 主題名和主題過濾器是UTF-8編碼字符串,它們不能超過65535字節

除了不能超過UTF-編碼字符串的長度限制之外,主題名或主題過濾器的層級數量沒有其它限制。

匹配訂閱時,服務端不能對主題名或主題過濾器執行任何規範化(normalization)處理,不能修改或替換任何未識別的字符。主題過濾器中的每個非通配符層級需要逐字符匹配主題名中對應的層級纔算匹配成功。

使用UTF-8編碼規則意味着,主題過濾器和主題名的比較可以通過比較編碼後的UTF-8字節或解碼後的Unicode字符。

  • “ACCOUNTS” 和 “Accounts” 是不同的主題名。
  • “Accounts payable” 是合法的主題名
  • “/finance” 和 “finance” 是不同的。

如果訂閱的主題過濾器與消息的主題名匹配,應用消息會被髮送給每一個匹配的客戶端訂閱。主題可能是管理員在服務端預先定義好的,也可能是服務端收到第一個訂閱或使用那個主題名的應用消息時動態添加的。服務端也可以使用一個安全組件有選擇地授權客戶端使用某個主題資源。

訂閱(MQTT 5)

MQTT提供兩種訂閱方式,共享和非共享

在MQTT 早期版本中,所有的訂閱都是非共享的。

非共享訂閱

非共享訂閱只與創建它的會話相關聯。每個訂閱(Subscription)包含一個指示用於在此會話上分發消息的主題過濾器和訂閱選項。服務端負責收集與過濾器相匹配的消息,並在此會話的連接上發送這些消息。

一個會話不能有多個包含相同主題過濾器的非共享訂閱,因此主題過濾器可以用作標識此會話的訂閱的關鍵詞。

如果有多個客戶端,每個客戶端都擁有對某個相同主題的非共享訂閱,則每個客戶端都將獲得在該主題上發佈的應用消息的副本。這意味着非共享訂閱不能被用於多個消費客戶端的應用消息負載均衡,因爲在這種情況下,每條消息都將被傳遞給每一個訂閱的客戶端。

共享訂閱

共享訂閱可以與多個訂閱會話相關聯。與非共享訂閱一樣,它包含一個主題過濾器和訂閱選項。但是,與此主題過濾器相匹配的發佈消息僅被髮布到其中一個訂閱會話。共享訂閱在多個消費客戶端並行共享處理髮布消息時是很有用的。

使用特殊樣式的主題過濾器來表示共享訂閱。過濾器格式如下:

$share/{ShareName}/{filter}

  • $share是字符串字面量,用來把主題過濾器標記爲共享訂閱主題過濾器。
  • {ShareName}是字符串,不包含/+#
  • {filter}該字符串的剩餘部分與非共享訂閱中的主題過濾器具有相同的語法和語義。

共享訂閱主題過濾器必須以$share/開始,且必須包含至少一個字符長度的共享名(ShareName) 。共享名不能包含字符"/","+“或”#",但必須跟在"/“字符後面。此”/"字符後面必須跟隨一個主題過濾器 。

共享訂閱在MQTT服務端的範圍內定義,而不是在會話中定義。共享訂閱的主題過濾器包含共享名,因此服務端可以有多個包含相同{過濾器}組件的共享訂閱。通常,應用程序使用共享名錶示共享同一個訂閱的一組訂閱會話。

示例

共享訂閱$hare/consumer1/sport/tennis/+$share/consumer2/sport/tennis/+是不同的共享訂閱,因此可以被關聯到不同的會話組。它們都與非共享訂閱主題sport/tennis/+相匹配。

如果一條消息被髮布到匹配主題sport/tennis/+,則消息的副本僅發送給所有訂閱$hare/consumer1/sport/tennis/+的會話中的一個會話,也僅發送給所有訂閱$share/consumer2/sport/tennis/+的會話中的一個會話。更多的副本將發送給所有對sport/tennis/+進行非共享訂閱的客戶端。

共享訂閱$share/consumer1//finance匹配非共享訂閱主題/finance

注意,$share/consumer1//finance$share/consumer1/sport/tennis/+是不同的共享訂閱,儘管它們有相同的共享名。它們可能在某種程度上是相關的,但擁有相同的共享名並不意味着它們之間有某種關係。

通過SUBSCRIBE請求中的共享訂閱主題過濾器創建共享訂閱。只有一個會話訂閱了某個共享訂閱時,共享訂閱行爲如同非共享訂閱,除了:

  • 匹配發布消息時,不考慮$share{ShareName} 部分。
  • 第一次訂閱時,保留消息不發送給此會話。其他匹配的發佈消息將發送給此會話。

一旦某個共享訂閱存在,其他會話就有可能訂閱了相同的共享訂閱主題過濾器。新的會話作爲額外的訂閱者關聯到此共享訂閱。保留消息不發送給此新的訂閱者。後續每條與此共享訂閱相匹配的應用消息被髮送到該共享訂閱關聯的其中一個會話。

會話可以通過發送包含某共享訂閱主題過濾器的UNSUBSCRIBE報文來顯式的將其從共享訂閱中分離。會話終止時,也將從共享訂閱中分離。

共享訂閱持續到至少有一個與其相關的會話(即,會話已經對此共享訂閱主題過濾器發佈了成功的SUBSCRIBE請求,且尚未完成相應的UNSUBSCRIBE)。當初始創建此共享訂閱的會話取消訂閱時,除非沒有其他的相關會話,否則共享訂閱仍然存在。共享訂閱在沒有被任何會話訂閱時結束,且任何相關的未分發的消息都被刪除。

共享訂閱註釋

  • 如果有不止一個會話訂閱了某個共享訂閱,服務端在消息的基礎上自由的選擇使用哪個會話,以及使用什麼標準來進行該選擇。
  • 允許不同的訂閱客戶端在其SUBSCRIBE報文中請求不同的QoS等級。服務端決定授予每個客戶端的最大QoS等級,並且允許向不同的訂閱者授予不同的最大QoS等級。向客戶端發送應用消息時,服務端必須考慮授予客戶端的QoS等級,與向訂閱者發送消息相同。
  • 如果服務端正在向其選中的訂閱客戶端發送QoS等級2的消息,並且在分發完成之前網絡中斷,服務端必須在客戶端重新連接時完成向該客戶端的消息分發。如果客戶端的會話在客戶端重連之前終止,服務端不能把此消息發送給其他訂閱的客戶端
  • 如果服務端正在向其選中的訂閱客戶端發送QoS等級1的消息,並且服務端在收到此客戶端的確認報文之前網絡中斷,服務端可以等客戶端重新連接之後將消息重傳給客戶端。如果客戶端的會話在客戶端重連之前終止,服務端應該把此應用消息發送給與此共享訂閱相關的另一個客戶端。服務端可以在第一個客戶端斷開連接時就嘗試將消息發送給另一個客戶端。
  • 如果客戶端對來自服務端的PUBLISH報文使用包含原因碼大於等於0x80的PUBACK或PUBREC報文進行響應,服務端必須丟棄應用消息而不嘗試將其發送給任何其他訂閱者。
  • 允許客戶端向已訂閱的共享訂閱第二次發送SUBSCRIBE請求。比如,它可以通過這樣改變其訂閱請求的QoS等級,或者因爲它不確定以前的連接關閉之前訂閱是否已完成。這不會增加共享訂閱關聯的會話個數,因此會話將在其第一次發送UNSUBSCRIBE之後脫離此共享訂閱。
  • 每個共享訂閱都是獨立於其他共享訂閱的。有可能兩個共享訂閱包含了重疊的過濾器。在這種情況下,與兩個共享訂閱都相匹配的消息都將被它們單獨處理。如果某個客戶端既有共享訂閱也有非共享訂閱,且某個消息與它們都相匹配,客戶端將由於存在非共享訂閱而接收此消息的副本,此消息的第二個副本將分發給此共享訂閱的某個訂閱者,因此可能導致兩份副本都被髮送給此客戶端。

流控(MQTT 5)

客戶端和服務端使用接收最大值來控制接收未被確認的PUBLISH報文數量。接收最大值創建了一個發送配額,用於限制可以在沒收到PUBACK(QoS等級1)或PUBCOMP(QoS等級2)的情況下發送的QoS等級大於0的PUBLISH報文數量。PUBACK和PUBCOMP按照下述方式補充配額。

客戶端或服務端必須將其初始發送配額設置爲不超過接收最大值的非0值。

每當客戶端或服務端發送了一個QoS等級大於0的PUBLISH報文,它就會減少發送配額。如果發送配額減爲0,客戶端或服務端不能再發送任何QoS等級大於0的PUBLISH報文。它可以繼續發送QoS爲0的PUBLISH報文,也可以選擇暫停發送這些報文。即使配額爲0,客戶端和服務端也必須繼續處理和響應其他MQTT控制報文。

發送配額增加1:

  • 每當收到一個PUBACK報文或PUBCOMP報文,不管PUBACK或PUBCOMP報文是否包含錯誤碼。
  • 每次收到一個包含返回碼大於等於0x80的PUBREC報文。

如果發送配額已到達初始發送配額,則不繼續增加。在初始發送配額之上嘗試增加配額可能是由建立新的網絡連接後重新發送PUBREL數據包引起的。

發送配額和接收最大值的保留不跨越網絡連接,每次建立新的網絡連接時按照上面的描述進行初始化。它們不是會話狀態的一部分。

請求/響應(MQTT 5)

有些應用程序或標準可能希望通過MQTT協議運行請求/響應交互。此版本MQTT協議包含三個可用於此目的的屬性:

  • 響應主題
  • 對比數據
  • 請求響應信息
  • 響應信息

示例

客戶端通過發佈一個包含響應主題的應用消息來發送請求消息。請求消息可以包含對比數據屬性。

基本請求響應(非規範)

請求/響應交互過程如下:

  1. MQTT客戶端(請求方)向主題發佈請求消息。請求消息是具有響應主題的應用消息。
  2. 另一個MQTT客戶端(響應方)訂閱了與請求消息發佈時使用的主題名相匹配的主題過濾器。結果,它收到請求消息。可能有多個響應方訂閱了此主題名,也可能沒有響應方。
  3. 響應方根據請求消息採取適當的操作,然後往請求消息中攜帶的響應主題屬性中的主題名發佈響應消息。
  4. 典型用法,請求放訂閱了響應主題,從而接收到響應信息。但是,其他某些客戶端可能會訂閱響應主題,因此它們也將接收和處理響應消息。與請求消息一樣,可能有多個客戶端訂閱了響應消息的發送主題,也可能沒有。

如果請求消息包含對比數據屬性,則響應方將此屬性拷貝到響應消息中,由響應消息的接收端用來將響應消息與原始請求相關聯。響應消息不包含響應主題屬性。

MQTT服務端轉發請求消息中的響應主題和對比數據屬性,和響應消息中的對比數據屬性。服務端像處理其他應用程序消息一樣處理請求消息和響應消息。

請求放通常在發佈請求消息之前訂閱響應主題。如果響應消息發送時沒有任何訂閱者訂閱了響應主題,則響應消息將不會傳遞給任何客戶端。

請求消息和響應消息可以具有任何QoS等級,並且響應方可以使用具有非0會話過期間隔的會話。通常使用QoS等級0發送請求消息,並且只有在應答者正連接時才發送請求消息。但這不是必須的。

響應者可以使用共享訂閱來允許響應客戶端池。注意,使用共享訂閱時,不保證消息在客戶端之間的分發順序。

請求方有責任確保它具有發佈消息到請求消息的主題、並訂閱響應主題屬性中主題名的必要權限。響應方有責任確保它具有訂閱請求主題和發佈到響應主題的權限。雖然主題授權不屬於MQTT規範中,但建議服務端實施此類授權。

確定響應主題值(非規範)

請求方可以通過包括本地配置在內的任何方式來確定作爲他們的響應主題的主題名。爲避免不同請求方之間的衝突,由請求方客戶端使用的響應主題最好對於該客戶端是唯一的。由於請求方和響應方通常都需要對這些主題進行授權,因此使用隨機主題名稱將會對授權造成挑戰。

爲了解決此問題,MQTT規範中在CONNACK報文中定義了一個名爲響應信息的屬性。服務端可以使用此屬性指導客戶端如何選擇使用的響應主題。此機制對於服務端和客戶端都是可選的。連接時,客戶端通過設置CONNECT報文中的請求響應信息屬性來請求服務端發送響應信息。這會導致服務端在CONNACK報文中插入響應信息屬性(UTF-8編碼的字符串)。

MQTT規範中不定義響應信息的內容,但它可以被用來傳遞主題樹的全局唯一部分,該部分至少在其會話的整個生命週期內保留給該客戶端。使用這種機制,可以在服務端而不是每個客戶端中完成該屬性的配置。

服務端重定向

服務端可以通過發送包含原因碼爲0x9C(使用其他服務端)或0x9D(服務端已移動)的CONNACK或DISCONNECT報文請求客戶端使用另一臺服務端。服務端發送這些原因碼時可以包含一個服務端參考列表屬性,用以說明客戶端應該使用的服務端位置。

原因碼0x9C (使用其他服務端) 指定客戶端應該臨時切換到另一臺服務端。另一臺服務端可能是客戶端已知的,也可能是由服務端參考所指定的。

原因碼0x9D (服務端已移動)指定客戶端應該永久切換到另一臺服務端。另一臺服務端可能是客戶端已知的,也可能是由服務端參考所指定的。

服務端參考列表是一個UTF-8編碼字符串,其值是一個由空格分隔開的參考列表列表。MQTT5不指定服務端參考的格式。

推薦每個參考包含名稱及可選的端口號。如果名稱包含冒號,則名稱字符串可以由方括號括起來(“[“和“]”)。由方括號括起來的名稱不能包含右方括號(“]”)字符,用於表示使用冒號分隔符的IPv6 地址。這是一個簡化版的URI授權。

服務端參考列表中的名字通常代表主機名、DNS名、SRV名或IP地址。跟隨冒號分隔符的通常是十進制端口號。如果端口信息來自於DNS(比如包含SRV)或者使用默認端口,則主機名後無需跟隨端口號。

如果給出了多個服務端參考列表,則期望客戶端選擇其中一個。

服務端參考列表示例如下:

myserver.xyz.org
myserver.xyz.org:8883
10.10.151.22:8883 [fe80::9610:3eff:fe1c]:1883

允許服務端不發送服務端參考列表,允許客戶端忽略服務端參考列表。此特徵可用於允許負載平衡,服務器重定位以及向服務器的客戶端預配。

增強認證

MQTT CONNECT報文使用用戶名和密碼字段支持基本的網絡連接認證。這些字段雖然稱爲簡單密碼認證,但可以被用來承載其他形式的認證,例如把密碼作爲令牌(Token)傳遞。

增強認證包含質詢/響應風格的認證,從而擴展了基本認證。它可能涉及在CONNECT報文之後、CONNACK報文之前的客戶端和服務端之間AUTH報文交換。

服務端通過在CONNECT報文中添加認證方法字段來啓動增強認證。此字段指定使用的認證方法。如果服務端不支持客戶端提供的認證方法,它可以發送一個包含原因碼0x8C(無效的認證方法)或0x87(未授權)的CONNACK報文,並且必須關閉網絡連接。

認證方法是客戶端和服務端關於認證數據中的數據和CONNECT報文中其他字段的含義,以及客戶端和服務端完成認證需要交換和處理的協議。

認證方法通常爲SASL(Simple Authentication and Security Layer)機制,使用一個註冊過的名稱便於信息交換。然而,認證方法不限於使用已註冊的SASL機制。

如果客戶端選擇的認證方法指定客戶端先發送數據,客戶端應該在CONNECT報文中包含認證數據屬性。此屬性可被用來提供認證方法指定的數據,認證數據的內容由認證方法定義。

如果服務端需要額外的信息來完成認證,它可以向客戶端發送AUTH報文,此報文必須包含原因碼0x18(繼續認證)。如果認證方法需要服務端向客戶端發送認證相關的數據,這些數據在認證數據(Authentication Data)中發送。

客戶端通過發送另一個AUTH報文響應來自服務端的AUTH報文,此報文必須包含原因碼0x18(繼續認證)。如果認證方法要求客戶端向服務端發送認證相關的數據,這些數據在認證數據(Authentication Data)中發送。

客戶端和服務端按需交換AUTH報文,直到服務端通過發送包含原因碼爲0的CONNACK報文接受認證爲止。如果接受認證需要向客戶端發送數據,這些數據在認證數據中發送。

客戶端可以在處理過程中隨時關閉連接。它可以在關閉之前發送DISCONNECT報文。服務端可以在處理過程中隨時拒絕認證。它可以發送包含原因碼大於等於0x80的CONNACK報文 ,並且必須關閉網絡連接。

如果初始CONNECT報文包含認證方法屬性,則所有的AUTH報文和成功的CONNACK報文必須包含與CONNECT報文中相同的認證方法屬性。

增強認證的實現對於客戶端和服務端來說都是可選的。如果客戶端在CONNECT報文中沒有包含認證方法,則服務端不能發送AUTH報文,且不能在CONNACK報文中發送認證方法 。如果客戶端在CONNECT報文中沒有包含認證方法,則客戶端不能向服務端發送AUTH報文 。

如果客戶端在CONNECT報文中沒有包含認證方法,服務端應該使用CONNECT報文中的信息、TLS會話和網絡連接進行認證。

SCRAM認證非規範示例

  • 客戶端到服務端:CONNECT認證方法=“SCRAM-SHA-1”,認證數據=client-first-data
  • 服務端到客戶端:AUTH原因碼=0x18,認證方法=“SCRAM-SHA-1”,認證數據=server-first-data
  • 客戶端到服務端:AUTH原因碼=0x18,認證方法=“SCRAM-SHA-1”,認證數據=client-final-data
  • 服務端到客戶端:CONNACK原因碼=0,認證方法=“SCRAM-SHA-1”,認證數據=server-final-data

Kerberos認證非規範示例

  • 客戶端到服務端:CONNECT認證方法=“GS2-KRB5”
  • 服務端到客戶端:AUTH原因碼=0x18,認證方法=“GS2-KRB5”
  • 客戶端到服務端:AUTH原因碼=0x18,認證方法=“GS2-KRB5”,認證數據=initial context token
  • 服務端到客戶端:AUTH原因碼=0x18,認證方法=“GS2-KRB5”,認證數據=reply context token
  • 客戶端到服務端:AUTH原因碼=0x18,認證方法=“GS2-KRB5”
  • 服務端到客戶端:CONNACK原因碼=0,認證方法=“GS2-KRB5”,認證數據=outcome of authentication

重新認證

如果客戶端在CONNECT數據報文中提供了身份驗證方法,則它可以在收到CONNACK後隨時啓動重新身份驗證。 它通過發送一個原因碼爲0x19的AUTH數據報文(重新認證)來做到這一點。 客戶端必須將驗證方法設置爲與最初用於驗證網絡連接的驗證方法相同的值。 如果身份驗證方法首先需要客戶端數據,則此AUTH數據報文將包含身份驗證數據的第一塊作爲驗證數據。

服務器通過向客戶端發送AUTH數據報文來響應此重新身份驗證請求,原因代碼爲0x00(成功)以指示重新身份驗證已完成,或者原因代碼爲0x18(繼續身份驗證)以指示需要更多身份驗證數據。 客戶端可以通過發送原因碼爲0x18的AUTH數據報文(繼續身份驗證)來響應其他身份驗證數據。 與原始身份驗證一樣,此流程繼續進行,直到重新身份驗證完成或重新身份驗證失敗爲止。

如果重新認證失敗,客戶端或服務端應該發送包含適當原因碼的DISCONNECT報文。並且必須關閉網絡連接。

如果重新認證失敗,客戶端或服務端應該發送包含適當原因碼的DISCONNECT報文。並且必須關閉網絡連接。

服務端可以通過拒絕重新認證來限制客戶端在重新認證中嘗試的更改範圍。例如,如果服務端不允許更改用戶名,它可以使任何嘗試更改用戶名的重新認證都失敗。

MQTT5的錯誤處理

無效報文和協議錯誤(MQTT 5)

客戶端或服務端對其收到的MQTT控制報文的檢查嚴格程度依賴:

  • 客戶端或服務端實現的大小
  • 實現支持的性能
  • 接收端對發送端發送的MQTT控制報文的信任程度
  • 接收端對用於分發MQTT控制報文的網絡的信任程度
  • 繼續處理錯誤報文的的後果

如果發送端遵守此規範,它將不會發送無效報文或導致協議錯誤。然而,如果客戶端在收到CONNACK報文之前發送MQTT控制報文,它可能會因爲錯誤的估計了服務端的性能而導致協議錯誤。

無效報文和協議錯誤使用的原因碼包括:

  • 0x81 無效報文
  • 0x82 協議錯誤
  • 0x93 超過接收最大值
  • 0x95 報文過大
  • 0x9A 不支持保留
  • 0x9B 不支持的QoS等級
  • 0x9E 不支持共享訂閱
  • 0xA1 不支持訂閱標識符
  • 0xA2 不支持通配符訂閱

當客戶端檢測到無效報文或協議錯誤,並且給出了相應的原因碼時,它應該關閉網絡連接。在AUTH報文出錯的情況下它可以在關閉網絡連接之前發送包含原因碼的DISCONNECT報文。在其他報文出錯的情況下它應該在關閉網絡連接之前發送包含原因碼的DISCONNECT報文。使用原因碼0x81(錯誤報文)或0x82(協議錯誤),除非包含更具體的原因碼。

當服務端檢測到無效報文或協議錯誤,並且MQTT規範中中給出了相應的原因碼時,它必須關閉網絡連接。在CONNECT報文出錯的情況下它可以在關閉網絡連接之前發送包含原因碼的CONNACK報文。在其他報文出錯的情況下它應該在關閉網絡連接之前發送包含原因碼的DISCONNECT報文。使用原因碼0x81(無效報文)或0x82(協議錯誤),除非包含更具體的原因碼。對其他會話沒有影響。

如果服務端或客戶端省略了檢查MQTT控制報文的某些特性,它可能無法檢測到某個錯誤,因此可能會導致數據被損壞。

其他錯誤(MQTT 5)

發送端無法預料到無效報文和協議錯誤以外的錯誤,因爲它可能有某些沒有告知發送端的約束。客戶端或服務端可能在接收時遇到短暫的錯誤,比如內存不足,導致無法成功的處理某個MQTT控制報文。

包含原因碼大於等於0x80的確認報文PUBACK,PUBREC,PUBREL,PUBCOMP,SUBACK,UNSUBACK表明收到了某個報文標識符的報文出錯。這不會影響其他會話或此會話上的其他報文。

CONNACK報文和DISCONNECT報文允許使用大於等於0x80的原因碼以指示網絡連接將被關閉。如果某個大於等於0x80的原因碼被指定,無論是否發送CONNACK報文或DISCONNECT報文,必須關閉網絡連接。發送這些原因碼不會影響任何其他會話。

如果控制報文包含多個錯誤,接收端可以按照任意順序對報文進行驗證,並對發現的任何錯誤採取適當的行爲。

MQTT3.1.1的錯誤處理

除非另有說明,如果服務端或客戶端遇到了協議違規的行爲,它必須關閉傳輸這個協議違規控制報文的網絡連接 。
客戶端或服務端實現可能會遇到瞬時錯誤(Transient Error)(例如內部緩衝區滿了的情況)導致無法成功處理MQTT報文。
如果客戶端或服務端處理入站控制報文時遇到了瞬時錯誤,它必須關閉傳輸那個控制報文的網絡連接 。如果服務端發現了瞬時錯誤,它不應該斷開連接或者執行任何對其它客戶端有影響的操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章