MQTT學習之協議介紹


此文為轉載

協議原文: http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html


摘要

MQ遙測傳輸(MQ Telemetry Transport,MQTT)是一個輕量級的基於代理的發布/訂閱式消息傳輸協議,它的設計目標是開放、簡單、輕量和易於實現。這些特征使它適用於各種受限環境,比如,但不限於:

  • 網絡代價昂貴,低帶寬或不可靠。
  • 在嵌入設備中運行,處理器和內存資源有限。

該協議的特性包括:

  • 使用發布/訂閱消息模式,提供一對多的消息分發,解除應用程序耦合。
  • 消息傳輸對有效載荷內容不可知。
  • 使用TCP/IP提供基礎網絡連接。
  • 有3個消息發布服務質量級別:
    • “至多一次”,消息發布完全依賴於底層TCP/IP網絡。消息有可能丟失或重復。這一級別可應用於如下情景,如環境傳感器數據,丟失一次讀記錄無所謂,因為很快下一次讀記錄就會產生。
    • “至少一次”,確保消息到達,但消息重復有可能發生。
    • “只有一次”,確保消息到達且只到達一次。這一級別可用於如計費系統等場景,在計費系統中,消息丟失或重復可能會導致生成錯誤的費用。
  • 輕量傳輸,開銷很小(固定頭部的長度只有2字節),協議交換最小化,以降低網絡流量。
  • 提供一種機制,當客戶端異常中斷時,利用 Last Will 和 Testament 特性來通知有關各方。

目錄

  1. 簡介
  1. 消息格式
  1. 命令消息
  1. 消息流
  1. 附錄A 主題通配符

1. 簡介

該協議規范主要分為3個主要部分:

  • 對所有類型的數據包都通用的消息格式
  • 每種特定數據包的具體細節
  • 數據包如何在服務器和客戶端之間傳輸

在附錄中將介紹如何主題通配符(topic wildcards)的使用方法。

1.1 V3.1V3之間的變化

MQTT V3.1中與MQTT V3之間的不同點如下所示:

  • 添加用戶驗證,用戶名和密碼現在可以在 CONNECT 數據包中一並發送。
  • 為解決一些安全問題,在 CONNACK 數據包中添加了新的返回碼。
  • 當客戶端發送未授權的 PUBLISH 或 SUBSCRIBE 命令時,客戶端不會收到相應的通知,即客戶端不知道命令不會被執行。而且即使命令未被執行,該MQTT流也應該正常完成。
  • MQTT中的字符串現在支持完整的UTF-8字符集,而不僅僅是US-ASCII子集。

V3.1中,通過 CONNECT 包傳送的協議版本號仍然是“3”,與V3相比沒有變化。現存的基於MQTT V3的服務器實現須通過正確考慮“剩余長度(Remaining Length)字段”,以及相應地忽略多余的安全信息來接受來自V3.1協議的客戶端的連接。

2. 消息格式

每個MQTT命令消息的消息頭部都包含了一個固定頭部。其中一些類型的消息可能還需要一個可變頭部和一個有效載荷(可理解為消息體)。消息頭部的具體格式將在后面章節中進行詳細介紹。

2.1 固定頭部(Fixed header

每個MQTT命令消息的消息頭部都包含一個固定頭部。固定頭部的格式如下表如示。

 

Byte 1

包含消息類型和標志(包括DUP,QoS level和RETAIN)字段。

Byte 2

包含剩余長度字段(至少1個字節,最多4個字節)。

以下章節將詳細介紹這些字段。所有數據的值都是以big-endian(大端)模式存儲:數據的高位字節存放在內存的低地址中,數據的低位字節存放在內存高地址中。一個16位字在內存中的存放順序是先最高有效位(MSB),然后再最低有效位(LSB)。

消息類型(Message Type

位置:byte 1, bits 7-4

該字段為4-bit無符號值。當前協議版本中該字段的具體枚舉值如下表所示。

 

0:保留

1:客戶端請求連接服務器

2:連接確認

3:發布消息

4:發布確認

5:發布接收(有保證的交付第1部分)

6:發布釋放(有保證的交付第2部分)

7:發布完成(有保證的交付第3部分)

8:客戶端訂閱請求

9:訂閱確認

10:客戶端取消訂閱請求

11:取消訂閱確認

12:PING請求

13:PING回復

14:客戶端斷開連接

15:保留

標志位(Flags

固定頭部第1個字節中剩余的部分包含DUP,QoS和RETAIN標志字段。相應bit位置如下表所示。

 

DUP

位置:byte 1,bit 3

當客戶端或服務器試圖重發 PUBLISH、PUBREL、SUBSRIBE、UNSUBSCRIBE 消息時,該標志位要被置位(即設為1)。這適用於消息的QoS標志值大於0的情況,此時消息確認是必需的。當DUP位被置位時,可變頭部將包含一個消息ID。

消息的接收者應當將該標志視為該消息之前可能已收到的提示消息,而不該依賴於它進行消息重復檢測。

QoS

位置:byte 1,bits 2-1

該標志位標明 PUBLISH 消息的交付質量級別。具體的QoS級別如下表所示。

 

RETAIN

位置:byte 1,bit 0

該標志位只用於 PUBLISH 消息。當一個客戶端發送一條 PUBLISH 消息給服務器,假設該消息所屬的主題(topic)為topicA,如果該標志位被置位(1),服務器在將該條消息發布給當前的所有topicA的訂閱者之后,還應當保持這條消息。

當topicA出現了一個新的訂閱者,則topicA的最后一條保持消息應當發給該訂閱者。當然,如果不存在保持消息,則什么也不用發。

當消息發布者以基於 “report by exception” 的方式發送消息時,這個功能就特別有用,因為這種情況下,消息發送間隔往往較長。這個功能使得新的訂閱者可以立刻收到之前保持的或上一個確定有效的消息。

當服務器收到某個主題的 PUBLISH 消息時,對於之前已經訂閱該主題的客戶端,服務器將給這些客戶端發送這一 PUBLISH 消息,發送前,服務器會將該消息的 RETAIN 標志置為0(即不置位),不管服務器之前收到該 PUBLISH 消息時其 RETAIN 標志是否被置位。這樣做可以使得區分它接收到的 PUBLISH 消息是服務器之前保持的(RETAIN標志置位)還是即時收到的(RETAIN標志不置位)。

保持消息應當在重啟服務器后仍能保留。

如果服務器收到有效載荷長度為0或重復主題的保持消息,服務器可以刪除該保持消息。

剩余長度(Remaining Length

位置:byte 2(從byte 2,最大可至byte 5)

該字段表示當前消息的剩余內容的字節數,包括可變頭部和有效載荷的數據。

該字段本身的字節數是根據可變頭部和有效載荷的長度不同而變化的。該可變長度編碼方案如下:每個字節的低7位(7-0位)編碼剩余長度的數據,第8位表示后面是否還有編碼剩余長度的字節。即每個字節編碼128個值和一個“延續位”。所以只用一個字節時,最大只可表示127字節的長度。

舉例如下,十進制數字64只需用1個字節來編碼,即0x40。 十進制數字321(=65 + 2x128)則需要用2個字節來編碼,其中第1個字節為1100 0001,該字節的低7位表示65,第8位表示后面還有字節;第2個字節為0000 0010,表示2x128。

協議限制該字段最大為4個字節,這允許應用程序發送的最大消息長度為268435455(256MB),即0xFF,0xFF,0xFF,0x7F。

下表給出了增加該字段的字節數時相應可表示的剩余長度值。

 

該編碼方案的算法如下所示,輸入為一個十進制數(X),輸出為編碼后的結果。

do

  digit = X MOD 128

  X = X DIV 128

  // if there are more digits to encode, set the top bit of this digit

  if ( X > 0 )

    digit = digit OR 0x80

  endif

  'output' digit

while ( X> 0 )

其中,MOD是模運算符(在C語言中相當於%),DIV表示整數除法(在C語言中相當於/),OR是位或運算符(在C語言中相當於|)。

相應地,剩余長度字段的解碼算法如下所示:

multiplier = 1

value = 0

do

  digit = 'next digit from stream'

  value += (digit AND 127) * multiplier

  multiplier *= 128

while ((digit AND 128) != 0)

其中,AND是位與運算符(在C語言中相當於&)。

當該解碼算法終止,value等於剩余長度的字節數。

值得注意的是,剩余長度編碼不是可變頭部的一部分。剩余長度的值不包括用於編碼剩余長度的字節數。這部分“增加的長度”(1-3字節)是固定頭部的一部分,而不屬於可變頭部。

譯者注:可認為剩余長度字段雖然是固定的,但該字段的長度卻是變化的(1-4字節)。

2.2 可變頭部(Variable header

某些類型的MQTT命令消息還包含了一個可變頭部,它位於固定頭部和有效載荷之間。

可變的剩余長度字段(1-4字節)不是可變頭部的一部分。剩余長度字段的值不包括該字段本身的長度。該值只包括可變頭部和有效載荷。更多細節可可見固定頭部

可變頭部中各個字段的格式將在后續章節中進行詳細介紹,介紹順序與該字段在可變頭部中的順序一致。

協議名稱(Protocol name

協議名稱字段只用於 MQTT CONNECT 消息的可變頭部中。該字段以UTF編碼方式顯示協議名稱:MQIsdp,大小寫如上所示。

協議版本(Protocol version

協議版本字段只用於 MQTT CONNECT 消息的可變頭部中。

該字段用8位無符號值來表示客戶端所使用的協議修訂級別。當前版本協議中該字段的值為3(0x03),如下表所示。

 

連接標志(Connect flags

該字段用於 CONNECT 消息的可變頭部中,占1字節,包括Clean session、Will、Will QoS和Retain標志。

Clean session 標志

位置:bit 1(在連接標志字節中,下同)

如果沒有被置位(即值為0),則當客戶端斷線時,服務器必須保存該客戶端的訂閱信息,包括斷線期間發布的該客戶端訂閱的主題中交付質量級別為QoS 1和QoS2的消息,這樣當客戶端重連時,這部分消息能確保被送達到客戶端。同時,服務器還必須保持客戶端在斷線的那個時刻正在傳輸中的消息的狀態,直到客戶端重新連接。

如果被置位(即值為1),則服務器必須丟棄任何之前保持的該客戶端的信息,將該連接視為“不存在(Clean)”。同時,當客戶端斷線時,服務器必須丟棄其所有狀態。

通常情況下,客戶端會一直在其中一種模式下操作,不會進行切換。該選擇取決於具體應用的需求。一個 Clean session 客戶端將不會收到過時的信息,且它每次重連時都需要重新訂閱主題。而一個 non-clean session客戶端則不會漏接任何它在斷線時服務器發布的交付質量級別為QoS 1和QoS 2的消息。QoS 0級別的消息由於只是盡可能的交付,所以它永遠不會被存儲保持。

這個標志以前被稱為“Clean start”。因為這個標志其實是作用於整個會話期間的,而不只是在連接的開始階段,為了明確這個事實,所以將它重命名為“Clean session”。

服務器可以提供一種管理機制,當確定一個客戶端將永遠不會重新連接時,可以清除該客戶端的存儲信息。

 

連接標志中的第0位在目前協議版本中沒有使用到,保留為將來使用。。

Will 標志

位置:bit 2

Will消息是指當服務器與客戶端通信過程中出現故障或客戶端在保活時間內沒有與服務器保持正常交流時,服務器特意發給客戶端的消息。當客戶端通過發送 DISCONNECT 消息正常斷開時,Will消息不會發送。

如果Will標志被置位,則Will QoS標志和Will Retain標志的設置將會發生作用,同時,在有效載荷里必須填寫Will主題和Will消息內容字段。

Will標志的格式如下表所示。

 

連接標志中的第0位在目前協議版本中沒有使用到,保留為將來使用。

Will QoS

位置:bit 4-3

Will QoS標志用來設置當客戶端異常離線時,服務器發送的Will消息的交付質量級別。Will消息的內容在客戶端發送的 CONNECT 消息里的有效載荷里填寫。

如果Will標志被置位,則Will QoS字段必須填寫,否則該字段的值將被忽略。

Will QoS字段的可選值有0(0x00),1(0x01)和2(0x02),格式如下表所示。

 

連接標志中的第0位在目前協議版本中沒有使用到,保留為將來使用。

Will Retain 標志

位置:bit 5

Will Retain標志指明服務器是否需要保持客戶端異常離線時發送給客戶端的Will消息。

如果Will標志被置位,則Will Retain標志必須填寫,否則其將被忽略。該標志的格式如下表所示。

 

連接標志中的第0位在目前協議版本中沒有使用到,保留為將來使用。

User name and password 標志

位置:bit 6和bit 7

客戶端在連接服務器時可以指定用戶名和密碼,通過將用戶名標志和密碼標志(可選)置位表明在 CONNECT 消息的有效載荷里包含有用戶名和密碼。

如果將用戶名標志置位,則必須在有效載荷里填寫用戶名字段,否則用戶名字段將被忽略。同樣地,如果密碼標志被置位,則必須在有效載荷里填寫密碼字段,否則密碼字段將被忽略。只提供密碼而不提供用戶名是不合法的。

 

連接標志中的第0位在目前協議版本中沒有使用到,保留為將來使用。

保活計時器(Keep Alive timer

保活計時器用於MQTT CONNECT 消息的可變頭部中。

保活計時器定義了服務器收到客戶端消息的最大時間間隔,它以秒為單位。它使得服務器不需要等待漫長的TCP/IP超時就可以檢測與客戶端的網絡連接是否斷開。客戶端有義務在每個保活時間間隔內至少發送一條消息給服務器。如果這期間沒有業務相關的消息要發送,客戶端則發送一個 PINGREQ 消息給服務器,相應地服務器返回一個 PINGRESQ 消息給客戶端。

如果服務器在1.5個保活時間(可寬容0.5個保活時間)內都沒有收到客戶端的消息,則服務器將其視為客戶端發送了一個 DISCONNECT 消息,並斷開與客戶端的連接。這個動作不影響客戶端的訂閱。具體細節參見 DISCONNECT

如果客戶端在發送 PINGQ 后的一個保活時間內沒有收到服務器發來的 PINGRESP 消息,則客戶端可以關閉TCP/IP套接字連接。

保活計時器用2個字節來表示,時間單位為秒。實際設定的值由特定應用決定,不過通常它的值都設為數分鍾,最大值接近18個小時。如果設為0,則表示客戶端不斷線。

保活計時器的格式如下表所示。2個字節的順序為先 MSB,再 LSB(大端模式)。

 

連接返回碼(Connect return code

連接返回碼用於 CONNACK 消息的可變頭部中。

這個字段用一個無符號字節來表示返回碼。這些值的含義如下表所示。值為0的返回碼通常表示連接成功。

 

主題名(Topic name

主題名用於 PUBLISH 消息的可變頭部中。

主題名決定消息要發送到哪個信息通道。訂閱者使用主題名來決定他們要從哪些信息通道接收消息。

主題名是一個UTF編碼字符串。更多信息參見MQTT和UTF-8。主題名支持的最大字符度為32767。

2.3 有效載荷(Payload

以下類型的MQTT命令消息擁有一個有效載荷:

CONNECT

該有效載荷包含了一個或多個UTF-8編碼字符串。它們包括標識客戶端的唯一標識符、Will主題和消息、要使用的用戶名和密碼。其中只有第一項是必選的,其余的取決於可變消息頭部中的標志置位情況。

SUBSCRIBE

該有效載荷包含一系列要訂閱的主題名,以及每個主題的QoS級別。這些字符串都是UTF編碼的。

SUBACK

該有效載荷包含一系列授權過的QoS級別。它們是服務器管理員允許授權給客戶端訂閱的各個主題的QoS級別。授權的QoS級別順序與相應訂閱的主題的順序保持一致。

PUBLISH

該有效載荷只包含應用特定的數據。協議不對數據的屬性和內容作任何假設,協議把消息的這部分內容視為一個BLOB。

如果你想要對有效載荷數據進行壓縮,你必須自己在有效載荷里定義合適的標志來處理壓縮事宜。你不能在固定狀況或可變頭部里定義與特定應用相關的標志。

2.4 消息標識符(Message Identifiers

消息標識符用於以下MQTT消息的可變頭部中:PUBLISH,PUBACK,PUBREC,PUBREL,PUBCOMP,SUBSCRIBE,SUBACK,UNSUBSCRIBE,UNSUBACK。

消息標識符(消息ID)字段只存在於固定頭部中QoS標志值為1或2的消息中。更多信息可參見交付質量級別和消息流

消息ID用16位無符號整數來表示,在同一個方向上的在傳消息ID必須是唯一的。它通常是逐個消息遞增的,但不強制如此。

客戶端與它所連接的服務器一樣,都需要維護自己的消息ID列表,二者的消息ID列表互不影響。客戶端在發送一個消息ID為1的 PUBLISH 消息的同時也有可能收到來自服務器的消息ID為1的 PUBLISH 消息。

表示消息ID的2個字節的順序為先 MSB,再 LSB(大端模式)。

不要使用值為0的消息ID。它是作為無效消息ID保留的。

 

2.5 MQTTUTF-8MQTT and UTF-8

UTF-8是一種針對Unicode的可變長度字符編碼,它優化了ASCII字符集的編碼,以支持基於文本的通信。

在MQTT中,字符串編碼的頭2個字節用來記錄字符串的長度,如下表所示。

 

字符串長度表示所有字符經過UTF-8編碼后的字節數,而不是字符串中字符的個數。例如,經過UTF-8編碼后的字符串 OTWP 如下表所示。

 

Java中的 writeUTF() 和 readUTF() 數據流方法也使用這種格式。

2.6 未使用的位(Unused bits

任何標明為未使用的位都應當置為0。

3. 命令消息(Command messages

3.1 CONNECT - 客戶端請求連接服務器

當客戶端與服務器的TCP/IP套接字連接建立起來之后,必須發送一個 CONNECT 消息流來建立一個協議級別的會話。

固定頭部(Fixed header

固定頭部的格式如下表所示。

 

DUP、QoS以及RETAIN標志在 CONNECT 消息中沒有被使用到。

剩余長度字段的值為可變頭部(12字節)和有效載荷的字節數總和。剩余長度字段自身的長度可能大於1個字節。

可變頭部(Variable header

一個可變頭部例子的格式如下表所示。

 

其中,

用戶名標志

置位(1)。

密碼標志

置位(1)。

Clean session標志

置位(1)。

保活計時器

設置為10秒(0x000A)。

Will 消息

  • Will標志置位(1)
  • Will QoS字段值為1
  • Will RETAIN標志不置位(0)

有效載荷(Payload

CONNECT 消息的有效載荷根據可變頭部中各標志位的置位情況,包含一個或多個UTF-8編碼字符串。這些字符串如果出現的話,必須符合以下順序:

客戶端ID

這是第1個UTF編碼字符串。客戶端ID(Client ID)的長度為1至23個字符,服務器根據客戶端ID可以指定到唯一的客戶端。對於連接到某個服務器的所有客戶端,它們的客戶端ID必須都是唯一的,客戶端ID還是處理QoS級別1和2消息的關鍵。如果發送的CONNECT 消息中客戶端ID的長度大於23個字符,則服務器會回復一個返回碼值為2(標識符被拒絕)的 CONNACK 消息。

Will主題

如果Will標志被置位,則Will主題將是有效載荷中的下一個字符串。Will消息將會發送給Will主題。消息的QoS級別和RETAIN狀態在可變頭部的Will QoS和Will RETAIN標志中設置。

Will消息(內容)

如果Will標志被置位,則Will消息將是有效載荷中的下一個字符串。Will消息定義了客戶端異常離線時服務器發送給Will主題的消息內容。當然,消息內容可以為空(消息長度為0,但該字符串仍包含2個字節以記錄其長度為0)。

盡管Will消息內容在 CONNECT 消息中是以UTF-8編碼的,但當它最后被發送到Will主題時,只有消息的實際內容被發送,而不包括開頭記錄長度的2個字節。因而,消息必須只包含7-bits ASCII字符。

用戶名

如果用戶名標志被置位,則用戶名將是有效載荷中的下一個字符串。用戶名字段用於認證,標明了連接的用戶的名字。建議用戶名不超過12個字符,但不強制如此。

值得注意的是,為了與MQTT V3版本協議兼容(V3中不支持用戶名密碼), 固定頭部中的剩余長度字段的優先級應該高於用戶名標志。服務器的實現必須允許用戶名標志被置位,但不存在用戶名字符串的情況。這是合法的,連接應該繼續進行。

密碼

如果密碼標志被置位,則密碼將是有效載荷中的下一個字符串。密碼字段用於認證,對應於連接的用戶名。建議密碼不超過12個字符,但不強制如此。

值得注意的是,為了與MQTT V3版本協議兼容(V3中不支持用戶名密碼), 固定頭部中的剩余長度字段的優先級應該高於密碼標志。服務器的實現必須允許密碼標志被置位,但不存在密碼字符串的情況。這是合法的,連接應該繼續進行。

回復(Response

服務器收到客戶端發送的 CONNECT 消息后會回復一個 CONNACK 消息。

如果在TCP/IP連接建立后的一段合理時間內服務器沒有收到客戶端發送的 CONNECT 消息,則服務器應該關閉這個連接。

如果客戶端在發送 CONNECT 消息后的一段合理時間內客戶端沒有收到服務器回復的 CONNACK 消息,則客戶端應該關閉原先的TCP/IP連接。然后建立新的TCP/IP連接,並發送 CONNECT 消息以重起會話。

以上兩種場景中,一段合理時間的設置依賴於特定應用的需求和通信架構。

如果嘗試連接的客戶端ID在服務器中已經存在,則服務器會斷開“舊”的客戶端並與新的客戶端完成連接操作。

如果客戶端發送了一個不合法的 CONNECT 消息,包括提供了不合法的協議名和協議版本號,服務器應該斷開連接。如果服務器可以從 CONNECT 消息中識別並且明確客戶端使用了一個不合法的協議,服務器可以嘗試在斷開連接前回復一個 CONNACK 消息,告知客戶端“Connection Refused: unacceptable protocol version”。

3.2 CONNACK - 連接確認

當客戶端向服務器發起 CONNECT 請求,服務器會回復其 CONNACK 消息。

固定頭部(Fixed header

固定頭部的格式如下表所示。

 

DUP、QoS以及RETAIN標志在 CONNACK 消息中沒有被使用到。

可變頭部(Variable header

可變頭部的格式如下表所示。

 

其中,連接返回碼用1個無符合字節表示,具體含義如下表所示。

 

如果客戶端ID的長度不在1-23字節之間,服務器則會發送返回碼2(標識符被拒絕)。

有效載荷(Payload

該類型消息沒有有效載荷。

3.3 PUBLISH - 發布消息

當客戶端想發布消息給感興趣的訂閱者時,客戶端將發送一條 PUBLISH 消息給服務器。每個 PUBLISH 消息都關聯一個主題名(也可稱為話題或頻道)。主題名是一個層次性的空間,它將訂閱者感興趣的信息資源進行分類。一個主題的消息將會發送給連接時訂閱了該主題的客戶端。

當客戶端訂閱了一個或多個主題,屬於這些主題的所有消息都將通過服務器發送相應 PUBLISH 消息給客戶端。

譯者注:其實流程簡單來說就是,發布者客戶端發送某topic的 PUBLISH 消息給服務器,然后服務器再發 PUBLISH 消息給所有該topic的訂閱者客戶端。

固定頭部(Fixed header

固定頭部的格式如下表所示。

 

其中,

QoS級別

設為1。詳見交付質量級別和消息流

DUP標志

設為0。表示該消息是第一次發送。詳見DUP

RETAIN標志

設為0。表示不保持。詳見RETAIN

剩余長度字段

長度包括可變頭部和有效載荷。字段自身長度為1-4字節。

可變頭部(Variable header

可變頭部包含以下字段:

主題名

一個UTF編碼字符串。

不允許出現主題通配符字符。

客戶端訂閱主題時主題名可以使用通配符,但服務器最終給訂閱客戶端發布 PUBLISH 消息時,消息里的主題名是和發布者客戶端發布的主題名一致的,也就是肯定不含有通配符。

消息ID

當消息的QoS級別為1或2時使用。詳見消息標識符

下表是一個 PUBLISH 消息例子的可變頭部。

 

該頭部的具體格式如下表所示。

 

有效載荷(Payload

包含要發的消息的數據。數據的內容和格式由應用決定。固定頭部中的剩余長度字段的值包括可變頭部和有效載荷。當然,有限載荷長度為0的 PUBLISH 消息也是有效的。

回復(Response

對PUBLISH 消息的回復取決於QoS級別。具體見下表。

 

動作(Action

PUBLISH 消息可以由發布者客戶端發給服務器,也可以由服務器發給訂閱者客戶端。PUBLISH 消息的接收者(包括服務器或客戶端)根據消息的QoS級別做出不同的反應。

QoS 0

僅僅將消息發送給所有相關的部分。

QoS 1

將消息持久化存儲,將消息發送給所有相關的部分,回復 PUBACK 消息給發送者。

QoS 2

將消息持久化存儲,先不將消息發送給所有相關的部分,而是先回復 PUBREC 消息給發送者。

如果是服務器收到 PUBLISH 消息,則此時所有相關部分指的是訂閱了該條 PUBLISH 消息主題的訂閱者客戶端。如果是訂閱者客戶端收到 PUBLISH 消息,則此時所有相關部分就是指客戶端上等待服務器消息的各個應用。

更多信息請參見交付質量級別和消息流

值得注意的是,如果一個服務器實現對某個客戶端發送的一個 PUBLISH 消息不允許授權,它沒有辦法通知客戶端。因此,當授權通過時,它需要做出一個正面的確認。根據正常的QoS規則,如果某個客戶端沒有發布 PUBLISH 消息的授權,這個客戶端不會被告知。

3.4 PUBACK - 發布確認

PUBACK 消息是對QoS級別1的 PUBLISH 消息的回應。當發布者客戶端發送 PUBLISH 消息給服務器,服務器有義務回復 PUBACK 消息。同樣地,當服務器發送 PUBLISH 消息給訂閱者客戶端,客戶端也有義務回復 PUBACK 消息。

固定頭部(Fixed header

固定頭部的格式如下表所示。

 

其中,

QoS級別

未使用。

DUP標志

未使用。

RETAIN標志

未使用。

剩余長度字段

因為沒有有效載荷,所有長度為可變頭部的長度(2字節)。

可變頭部(Variable header

包含被確認的 PUBLISH 消息的消息標識符(消息ID)。格式如下表所示。

 

有效載荷(Payload

沒有有效載荷。

動作(Action

當發布者客戶端收到 PUBACK 消息,則它丟棄自己原先發送給服務器的 PUBLISH 消息,因為該消息已經被服務器接收並存儲了。

3.5 PUBREC - 發布接收(有保證的交付第1部分)

PUBREC 消息是對QoS級別2的 PUBLISH 消息的回應。它是QoS級別2協議流中的第2個消息。PUBREC 消息可以是服務器對發布者客戶端發送的 PUBLISH 消息的回應,也可以是訂閱者客戶端對服務器發送的 PUBLISH 消息的回應。

固定頭部(Fixed header

固定頭部的格式如下表所示。

 

其中,

QoS級別

未使用。

DUP標志

未使用。

RETAIN標志

未使用。

剩余長度字段

因為沒有有效載荷,所有長度為可變頭部的長度(2字節)。

可變頭部(Variable header

包含被確認的 PUBLISH 消息的消息標識符(消息ID)。格式如下表所示。

 

有效載荷(Payload

沒有有效載荷。

動作(Action

當接收者收到 PUBREC 消息,則它將回復一個 PUBREL 消息,消息中攜帶的消息ID與收到的 PUBREC 消息中的相同。

3.6 PUBREL - 發布釋放(有保證的交付第2部分)

PUBREL 消息可以是發布者客戶端對服務器發送給它的 PUBREC 消息的回應,也可是服務器對訂閱者客戶端發送給它的 PUBREC 消息的回應。它是QoS級別2協議流中的第3個消息。

固定頭部(Fixed header

固定頭部的格式如下表所示。

 

其中,

QoS級別

設為1。PUBREL 消息使用QoS級別1,期望得到 PUBCOMP 消息回應。它的重傳方式與 PUBLISH 消息一樣。

DUP標志

設為0。表示該消息是第一次發送。詳見DUP

RETAIN標志

未使用。

剩余長度字段

因為沒有有效載荷,所有長度為可變頭部的長度(2字節)。

可變頭部(Variable header

包含被確認的 PUBREC 消息的消息標識符(消息ID)。格式如下表所示。

 

有效載荷(Payload

沒有有效載荷。

動作(Action

當服務器收到發布者客戶端發送的 PUBREL 消息,服務器會將之前收到 PUBLISH 消息發送給所有相關的訂閱者,然后發送 PUBCOMP 消息給發布者客戶端,其中攜帶的消息ID與客戶端發送的 PUBLISH 消息ID相同。當訂閱者客戶端收到服務器發送的 PUBREL 消息后,客戶端將消息發送給訂閱的應用,然后發送 PUBCOMP 消息給服務器。

3.7 PUBCOMP - 發布完成(有保證的交付第3部分)

PUBCOMP 消息可以是服務器對發布者客戶端發送給它的 PUBREL 消息的回應,也可以是訂閱者客戶端對服務器發送給它的 PUBREL 消息的回應。它是QoS級別2協議流中的第4個消息。

固定頭部(Fixed header

固定頭部的格式如下表所示。

 

其中,

QoS級別

未使用。

DUP標志

未使用。

RETAIN標志

未使用。

剩余長度字段

因為沒有有效載荷,所有長度為可變頭部的長度(2字節)。

可變頭部(Variable header

包含被確認的 PUBREL 消息的消息標識符(消息ID)。格式如下表所示。

 

有效載荷(Payload

沒有有效載荷。

動作(Action

當發布者客戶端收到 PUBCOMP 消息,它將丟棄自己原發發送的 PUBLISH 消息,因為該消息已經被正確地發送一次到服務器。

3.8 SUBSCRIBE - 客戶端訂閱主題

SUBSCRIBE 消息允許客戶端向服務器訂閱一個或多個感興趣的主題。這些主題的發布消息都會通過PUBLISH 消息從服務器發送給客戶端。SUBSCRIBE 消息還可以分別對各個主題指定客戶端期望得到的QoS級別。

固定頭部(Fixed header

固定頭部的格式如下表所示。

 

其中,

QoS級別

SUBSCRIBE 消息使用QoS級別1來進行訂閱請求。相應的 SUBACK 消息攜帶與之相同的消息ID。重傳方式與 PUBLISH 消息相同。

DUP標志

設為0。表示該消息第一次被發送。詳見DUP

RETAIN標志

未使用。

剩余長度字段

長度包括可變頭部和有效載荷(原文:The length of the payload)。字段自身長度為1-4字節。

可變頭部(Variable header

因為 SUBSCRIBE 消息使用QoS級別1,所以可變頭部里包含有一個消息ID。詳見消息標識符

下表是一個可變頭部例子的格式。

 

有效載荷(Payload

SUBSCRIBE 消息的有效載荷包括了一系列客戶端想要訂閱的主題,以及對各個主題,客戶端期望接收消息的QoS級別。字符串經過UTF編碼,指定的QoS級別雖然只用2bits表示,但仍要占用一個字節。主題字符串可以包含主題通配符來訂閱一系列主題集。這些主題/QoS對的形式如下表中的例子所示。

 

SUBSCRIBE消息里的主題名沒有被壓縮。

上個例子的具體格式如下表所示。

 

假設客戶端訂閱請求的QoS級別通過授權,該客戶端之后收到的 PUBLISH 消息的QoS級別將等於或小於請求的QoS級別,這取決於消息發布者設定的消息QoS級別。例如,如果客戶端申請訂閱某主題的QoS級別為1,對於QoS級別為0的該主題的發布消息,該客戶端只能以QoS級別0來接收。而對於QoS級別為2的該主題的發布消息,該客戶端將降格以QoS級別1來接收。

如果客戶端以QoS級別2來訂閱某主題,客戶端相當於說:“我願意以發布消息時設置的QoS級別來接收這個主題的消息。”。(譯者注:因為級別2為最高級,發布消息不可能被降格。)

這意味着,發布者有責任檢測能被接收的最高QoS級別。不過,訂閱者可以根據自己的需求來降格QoS級別。消息的QoS級別不可能被升格。

請求的QoS字段跟隨着每個UTF編碼的主題名字符串之后,其格式如下表所示。

 

當前版本協議中該字節的前6位未被使用,保留作將來使用。

如果最后2位都被置位(即值為3),則應當將其視為非法包並關閉連接。

回復(Response

當服務器收到來自客戶端的 SUBSCRIBE 消息,應當回復一個 SUBACK 消息。

在客戶端收到服務器發送的 SUBACK 消息之前,服務器就可以向客戶端的訂閱發送 PUBLISH 消息。

值得注意的是,如果一個服務器的實現不允許授權客戶端的 SUBSCRIBE 請求,它沒有辦法告知客戶端。因此,當授權通過時,它必須發送 SUBACK 消息來進行正面的回應。如果訂閱沒有通過授權,客戶端不會被告知。

服務器可以以低於客戶端申請的QoS級別進行授權。當服務器沒有辦法提供高QoS級別時,這種情況就可能發生。例如,如果服務器不提供可靠的持久化存儲,它可以選擇只能QoS級別0來授權訂閱。

3.9 SUBACK - 訂閱確認

服務器通過發送 SUBACK 消息來確認其已收到 SUBSCRIBE 消息。

SUBACK 消息包含一系列通過授權的QoS級別。它們的順序與 SUBSCRIBE 消息里訂閱的主題的順序一致。

固定頭部(Fixed header

固定頭部的格式如下表所示。

 

其中,

QoS級別

未使用。

DUP標志

未使用。

RETAIN標志

未使用。

剩余長度字段

長度包括可變頭部和有效載荷(原文:The length of the payload)。字段自身長度為1-4字節。

可變頭部(Variable header

可變頭部包含了確認的 SUBSCRIBE 消息的消息ID,格式如下表所示。

 

有效載荷(Payload

有效載荷包含了一組通過授權的QoS級別。各個QoS級別對應 SUBSCRIBE 消息里的主題名。SUBACK 消息里QoS級別的順序與 SUBSCRIBE 消息里申請的主題名/QoS對相匹配。可變頭部里的消息ID可以讓你匹配到對應的 SUBSCRIBE 消息。

每個通過授權的QoS字段用一個字節來編碼,如下表所示。

 

當前版本協議中該字節的前6位未被使用,保留作將來使用。

一個有效載荷的例子如下表所示。

 

它的具體格式如下表所示。

 

3.10 UNSUBSCRIBE - 客戶端取消訂閱主題

客戶端可以通過發送 UNSUBSCRIBE 消息給服務器來退訂相應的主題。

固定頭部(Fixed header

固定頭部的格式如下表所示。

 

其中,

QoS級別

UNSUBSCRIBE 消息使用QoS級別1來進行退訂的申請。對應的 SUBACK 消息將用消息ID來識別。重傳方式與 PUBLISH 消息相同。

DUP標志

設為0。表示該消息是第一次發送。詳見DUP

RETAIN標志

未使用。

剩余長度字段

長度包括可變頭部和有效載荷(原文:This the length of the payload)。字段自身長度為1-4字節。

可變頭部(Variable header

因為 UNSUBSCRIBE 消息使用QoS級別1,所以可變頭部里包含一個消息ID。詳見消息標識符

下表是一個消息ID為10的可變頭部例子的格式。

 

有效載荷(Payload

有效載荷里包含客戶端要退訂的一系列主題名。這些字符串是UTF編碼的,並且是連接的。UNSUBSCRIBE 消息里的主題名沒有被壓縮。一個有效載荷的例子如下表所示。

 

它的具體格式如下表所示。

 

回復(Response

服務器在收到客戶端的 UNSUBSCRIBE 消息后,將回復 UNSUBACK 消息。

3.11 UNSUBACK - 取消訂閱確認

服務器通過發送 UNSUBACK 消息來確認其已收到 UNSUBSCRIBE 消息。

固定頭部(Fixed header

固定頭部的格式如下表所示。

 

其中,

QoS級別

未使用。

DUP標志

未使用。

RETAIN標志

未使用。

剩余長度字段

因為沒有有效載荷,所有長度為可變頭部的長度(2字節)。

可變頭部(Variable header

可變頭部包含要確認的 UNSUBSCRIBE 消息的消息ID,它的格式如下表所示。

 

有效載荷(Payload

沒有有效載荷。

3.12 PINGREQ - PING請求

PINGREQ 消息從連接中的客戶端發送給服務器,表示詢問服務器:“你還活着嗎”?

更多信息參見保活計時器

固定頭部(Fixed header

固定頭部的格式如下表所示。

 

其中,DUP,QoS以及RETAIN標志未使用。

可變頭部(Variable header

沒有可變頭部。

有效載荷(Payload

沒有有效載荷。

回復(Response

PINGREQ 消息的期望回復是 PINGRESP 消息。

3.13 PINGRESP - PING回復

PINGRESP 消息是服務器對客戶端發送給它的 PINGREQ 消息的回應,表示回答客戶端:“對,哥還活着”。

更多信息參見保活計時器

固定頭部(Fixed header

固定頭部的格式如下表所示。

 

其中,DUP,QoS以及RETAIN標志未使用。

可變頭部(Variable header

沒有可變頭部。

有效載荷(Payload

沒有有效載荷。

3.14 DISCONNECT - 斷開連接通知

當客戶端想要斷開它的TCP/IP連接時,通過向服務器發送 DISCONNECT 消息告知服務器。這是為了干凈地斷開連接,而不僅僅是斷開連線。

如果客戶端在連接時將clean session標志置位,則之前保持的客戶端的信息將會被丟棄。

服務器不應該僅僅依賴客戶端發送的 DISCONNECT 消息來關閉TCP/IP連接。

固定頭部(Fixed header

固定頭部的格式如下表所示。

 

其中,DUP,QoS以及RETAIN標志未使用。

可變頭部(Variable header

沒有可變頭部。

有效載荷(Payload

沒有有效載荷。

4. 消息流(Flows

4.1 交付質量級別和消息流(Quality of Service levels and flows

MQTT 根據交付質量級別(QoS)來發送消息。所有級別描述如下所示:

QoS 級別0: 至多交付一次

消息依賴底層的TCP/IP網絡做盡可能的發送。不要求響應且協議中也沒有定義重傳的語義。該消息要么只到達服務器1次,要么干脆一次也沒有。

QoS級別0的協議流如下表所示。

 

QoS 級別1:至少交付一次

服務器確認收到 PUBLISH 消息后要回復客戶端一個 PUBACK 消息。不管是通信鏈路還是發送設備出現了故障,或是確認消息沒有在規定時間內收到,則發送者(即之前發送 PUBLISH 消息的客戶端)需要重發 PUBLISH 消息,且該消息頭部中的DUP標志要置位。該消息將至少到達服務器一次。SUBSCRIBE 和 UNSUBSCRIBE 消息都需要使用QoS級別1。

QoS級別1的消息頭部里都包含有一個消息ID。

QoS級別1的協議流如下表所示。

 

如果客戶端沒有收到 PUBACK 消息(不管是超過應用設定的時間還是檢測到故障然后通信會話重起),客戶端將重發DUP標志被置位的 PUBLISH 消息。

當服務器接收到一條客戶端發來的重復消息,服務器仍然會將該重復消息發送給訂閱者,然后再發送一條 PUBACK 消息給客戶端。

QoS 級別2:正好交付一次

QoS級別2是在QoS級別1上更進一步的協議流,可以保證重復消息不會發送給接收端應用。這是最高級別的交付質量,用在重復消息不可接受的情況下。雖然使用該級別將帶來網絡流量的增長,但對於一些重要的消息內容這通常是可以接受的。

QoS級別2的消息頭部里都包含有一個消息ID。

QoS級別2的協議流如下表所示。對於接收方有2種方式(語義)用來處理該 PUBLISH 流。這2種不同語義影響的是協議流中訂閱者可以接收消息的時間點。這2種語義都可以保證QoS級別2的協議流,如何選擇取決於具體的實現。

 

如果檢測到故障,或發生超時,協議流將重發最后一個未確認的協議消息,包括 PUBLISH 或 PUBREL。具體細節見消息重傳。該級別中附加的協議流可以保證消息只交付給訂閱者一次。

QoS級別12的假設

在任何網絡中,設備或通信鏈路都有可能發生故障。發生故障時,鏈路的一端可能並不知道另一端發生了什么事,這被稱為懷疑窗口(in doubt windows)。在這種背景下,對於消息發送,必須對設備和網絡的可靠性作一個假設。

MQTT假設客戶端和服務器在一般情況下是可靠的,而通信通道更可能是不可靠的。如果客戶端發生故障,則通常是災難性的故障,而不大可能是暫時性的。從設備中恢復數據的可能性是很低的。有些設備具有非易失性存儲,比如閃存ROM。為了應對各種可能的故障,需要提供更多的持久化存儲來保護最重要的數據。

除了最基本的通信鏈路的故障,更多超出MQTT處理能力的場景將使得故障模式矩陣顯得更加復雜。

4.2 消息重傳

盡管TCP通常可以保證數據包的送達,然而MQTT消息沒有被收到的情況無疑是存在的。對於需要收到回應的MQTT消息(QoS>0,PUBLISH、PUBREL、SUBSCRIBE、UNSUBSCRIBE),如果在規定時間內消息響應沒收到,發送方將重發該消息,且該消息的DUP標志將被置位。

重發超時應當是可配置的。超時的設置最好可以保證消息的正常傳輸不會引發超時。例如,通過一條擁擠的網絡發送一條大消息的時間通常要長於通過一條快速的網絡發送一條小消息的時間。一直重復地重傳一條超時的消息將使情況變得更糟糕。因此,隨着重傳次數增加而增大超時的策略通常會被采用。

當一個客戶端重新連接時,如果Clean session標志沒有被置位,則客戶端和服務器雙方都應當重發斷線前正在傳輸中的消息。

不同於上述“重連”重傳行為,客戶端不需要重發消息。然而,Brokers需要重發所有未確認的消息。

4.3 消息排序

消息的順序會被一系列因素所影響,包括客戶端允許同時在傳的 PUBLISH 流的數量和客戶端是單線程還是多線程的。為了討論的簡單,我們假設客戶端在對網絡中的數據包進行讀寫時是單線程的。

對於一個提供有序消息保證的實現,它必須保證消息流的每個階段的完成順序都應該與它們的啟動順序保持一致。例如,對於一系列QoS級別2的消息流,它們發送 PUBREL 消息的順序與它們發送 PUBISH 消息的順序保持一致:

 

客戶端允許同時在傳的消息數量(稱為在傳窗口)對保證交付的類型也有一定的影響:

  • 對於在傳窗口為1的情況,每個消息流的開始都在上個消息流完成之后。這保證了消息的傳送順序與提交它們的順序保持一致。
  • 當在傳窗口大於1時,消息的有序化只能通過QoS級別來保證。

附錄 A - 主題通配符(Topic wildcards

訂閱的主題可以包含特殊字符,從而可以使你一次訂閱多個主題。

主題層次分隔符是用來引入主題的結構,因而可以用來區分主題內的不同目的。多層次通配符和單層次通配符可以用於訂閱,但它們不能用於在一個主題內發布消息。

主題層次分隔符(Topic level separator

斜線(/)是用來分隔一棵主題樹里的各個層次,它提供了一個層次結構的主題空間。主題層次分隔符的引入為解決主題沖突有重要的意義。

多層次通配符

數字符號(#)即多層次通配符,用來匹配一個主題內的任意層次。例如,如果你想要訂閱了 finance/stock/ibm/#,則你可以收到如下主題的消息:

finance/stock/ibm

finance/stock/ibm/closingprice

finance/stock/ibm/currentprice

多層次通配符可以表示0個或任意多個層次。因而, finance/# 也可以匹配單單 finance 主題,這時, # 代表0層次。當然,在這種情況下,多層次通配符的使用是沒有意義的,因為根本沒有層次需要分隔。

多層次通配符只有2種用法,一是只有它自己本身,二是放在主題層次分隔符之后。因此, # 和 finance/# 都是合法的,而 finance#則是不合法的。多層次通配符在主題樹使用時必須作為最后一個字符。例如, finance/# 是合法的,而 finance/#/closingprice 是不合法的。

單層次通配符

加號(+)是用來匹配一個主題層次的通配符。例如, finance/stock/+ 可以匹配 finance/stock/ibm 和 finance/stock/xyz ,但不能匹配 finance/stock/ibm/closingprice。同時,因為單層次通配符必須匹配一個層次,所以 finance/+ 不能匹配 finance 。

單層次通配符可以用於主題樹中的任意層次,而且可以與多層次通配符配合使用。與多層次通配符的用法一樣,單層次通配符要么只有它自己,要么要放在主題層次分隔符之后。因而, + 和 finance/+ 都是合法的,而 finance+ 是不合法的。單層次分隔符即可以放在主題樹的最后,也可以放在主題樹的中間。例如, finance/+ 和 finance/+/ibm 都是合法的。

主題語義和慣用法(Topic semantics and usage

當你在構建一個應用時,主題樹的設計可以參考以下關於主題名語法和語義的設計原則:

  • 一個主題名必須不小於1個字符。
  • 主題名是大小寫敏感的。例如, ACCOUNTS 和 Accounts 是兩個不同的主題。
  • 主題名可以包含空格。例如, Accounts payable 應當是一個合法的主題名。
  • 以“/”開頭的主題名和不以“/”開頭的相同主題名是有區別的。例如, /finance 和 finance 是不同的, /finance 匹配於 +/+ 和 /+ , 但不匹配於 + 。
  • 不要在任何主題名中包含 `null` 字符(Unicode \x0000)。

以下原則適用於主題樹的構建和內容:

  • 長度被限制為64K,但主題樹中的層次數量沒有限制。
  • 可以有任意數量的根節點,也就是說,允許有任意數量的主題樹。

 


免責聲明!

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



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