一、MQTT協定簡介
MQTT(Message Queuing Telemetry Transport,消息隊列遙測傳輸協定),是一種基于釋出/訂閱(publish/subscribe)模式的"輕量級"通訊協定,該協定建構于TCP/IP協定 上,由IBM在1999年釋出。

MQTT最大優點在于,可以以極少的代碼和有限的帶寬,為連接配接遠端裝置提供實時可靠的消息服務。作為一種低開銷、低帶寬占用的即時通訊協定,使其在物聯網、小型裝置、移動應用等方面有較廣泛的應用。
相較于HTTP
在傳統網際網路應用中,HTTP被廣泛應用。通常情況下,用戶端的裝置配置、網絡環境都比較可控,另外請求通常會基于各種業務傳遞大量的資料。
但是對于物聯網來說,網絡不穩定、裝置能力不夠,是重點考慮的因素。再者物聯網傳輸的消息大小遠小于傳統網際網路的業務資料,如果每次發送資料都來一次連接配接/斷開TCP,發送的資料越多,資料總通信量也就越大。
再有就是在靈活性上HTTP的請求-響應模式也不如MQTT的釋出/訂閱模式。必須由裝置主動向伺服器發送資料,而伺服器難以主動向裝置推送資料。對于單單的資料采集等場景還勉強适用,但是對于頻繁的操控場景,隻能推過裝置定期主動拉取的的方式,實作成本和實時性都大打折扣。
二、MQTT協定設計規範
由于物聯網的環境是非常特别的,是以MQTT遵循以下設計原則:
- 精簡,不添加可有可無的功能;
- 釋出/訂閱(Pub/Sub)模式,友善消息在傳感器之間傳遞;
- 允許使用者動态建立主題,零運維成本;
- 把傳輸量降到最低以提高傳輸效率;
- 把低帶寬、高延遲、不穩定的網絡等因素考慮在内;
- 支援連續的會話控制;
- 了解用戶端計算能力可能很低;
- 提供服務品質管理;
- 假設資料不可知,不強求傳輸資料的類型與格式,保持靈活性。
發明人當時正在開發一個利用衛星通訊監控輸油管道的項目。為了實作這個項目要求,他們需要開發一種用于嵌入式裝置的通訊協定。 從誕生之初就是專為低帶寬、高延遲或不可靠的網絡而設計的。雖然曆經幾十年的更新和變化,以上這些特點仍然是MQTT協定的核心特點。但是與最初不同的是,MQTT協定已經從嵌入式系統應用拓展到開放的物聯網(IoT)領域。
版本
目前MQTT主流版本有兩個,分别是
MQTT3.1.1
和
MQTT5
。
MQTT3.1.1
是在2014年10月釋出的,而
MQTT5
是在2019年3月釋出的。
MQTT5是在
MQTT3.1.1
的基礎上進行了更新,添加了更多的功能、完善 MQTT 協定。是以MQTT5是完全相容
MQTT3.1.1
的。
三、MQTT與消息隊列MQ的差別
MQTT 并不是消息隊列,盡管兩者的很多行為和特性非常接近,比如都采用釋出訂閱模式等,但是他們面向的場景有着顯著的不同。
- 消息隊列主要用于服務端應用之間的消息存儲與轉發,這類場景往往資料量大但接入量少。
- MQTT 面向的是 IoT 領域和移動網際網路領域,這類場景的側重點是海量的裝置接入、管理與消息傳輸。
在實際的場景中,兩者往往被結合起來使用,譬如先由 MQTT Broker 接收物聯網裝置上傳的資料,然後通過消息隊列MQ将這些資料轉發到具體應用進行處理。
四、MQTT協定對于車聯網
在車聯網中有一個重要角色:TSP(Telematics Service Provider)汽車遠端服務提供商。TSP 上接汽車、車載裝置制造商、網絡營運商,下接内容提供商。
既然身處核心地位,自然少不了與其他環節的各種互動,比如說雲平台與車機端的消息接入。
而 MQTT 是基于釋出/訂閱模式的物聯網通信協定,具有簡單易實作、支援 QoS、封包小等特點,在車聯網場景中,MQTT 能夠勝任海量車機系統靈活、快速、安全地接入,并保證複雜網絡環境下消息實時性、可靠性。主要優勢:
- 開放消息協定,簡單易實作。市場上有大量成熟的軟體庫與硬體模組,可以有效降低車機接入難度和使用成本;
- 提供靈活的釋出訂閱和主題設計,能夠通過海量的 Topic 進行消息通信,應對各類車聯網業務;
- Payload 格式靈活,封包結構緊湊,可以靈活承載各類業務資料并有效減少車機網絡流量;
- 提供三個可選的 QoS 等級,能夠适應車機裝置不同的網絡環境;
- 提供線上狀态感覺與會話保持能力,友善管理車機線上狀态并進行離線消息保留。
五、MQTT協定特性介紹
1. 用戶端與伺服器
實作MQTT協定需要用戶端和伺服器端通訊完成。在通訊過程中,MQTT協定中有三種身份 :
釋出者(Publish)
、
代理(Broker)(伺服器)
、
訂閱者(Subscribe)
。
其中,消息的釋出者和訂閱者都是用戶端,消息代理是伺服器。
用戶端
MQTT用戶端可以向服務端釋出資訊,也可以從服務端收取資訊。
我們把用戶端發送資訊的行為稱為釋出資訊。而用戶端要想從服務端收取資訊,則首先要向服務端訂閱資訊。訂閱資訊這一操作很像我們在視訊網站訂閱某一類型電視劇。當這部電視劇上新後,視訊網站會向訂閱了該劇的使用者發送資訊,告訴他們有新劇上線了。
- 釋出其他用戶端可能會訂閱的資訊
- 訂閱其它用戶端釋出的消息
- 退訂或删除應用程式的消息
- 斷開與伺服器連接配接
伺服器
MQTT 服務端通常是一台伺服器,也稱為
“消息代理”(Broker)
。位于消息釋出者和訂閱者之間,它是MQTT資訊傳輸的樞紐,負責将MQTT用戶端發送來的資訊傳遞給MQTT用戶端。
MQTT 服務端還負責管理 MQTT 用戶端。確定用戶端之間的通訊順暢,保證 MQTT 消息得以正确接收和準确投遞。
- 接受來自客戶的網絡連接配接
- 接受客戶釋出的應用資訊
- 處理來自用戶端的訂閱和退訂請求
- 向訂閱的客戶轉發應用程式消息
2.主題
剛剛我們在講解 MQTT 用戶端訂閱資訊時,使用了使用者在視訊網站訂閱電視劇這個例子。在 MQTT 通訊中,用戶端所訂閱的肯定不是一部部電視劇,而是一個個主題。MQTT 服務端在管理 MQTT 資訊通訊時,就是使用主題來控制的。
再來舉個例子:
在以上圖示中一共有三個MQTT用戶端: 它們分别是汽車,手機和電腦。
假設我們需要利用手機和電腦擷取汽車的速度,那麼我們首先要利用電腦和手機向MQTT伺服器訂閱主題“汽車速度”。
接下來,當汽車用戶端向服務端的“汽車速度”主題釋出資訊後,服務端就會首先檢查以下都有哪些用戶端訂閱了“汽車速度”這一主題的資訊。
當它發現訂閱了該主題的用戶端有一個手機和一個電腦,于是服務端就會将剛剛收到的“汽車速度”資訊轉發給訂閱了該主題的手機和電腦用戶端。
在以上執行個體中,汽車是“汽車速度”主題的釋出者,而手機和電腦則是該主題的訂閱者。
另外,消息釋出者同時也可以是訂閱者 ,看圖:
上圖中的所有用戶端都是圍繞“空調溫度”這一主題進行通訊的。
對于“空調溫度”這一主題,手機和電腦用戶端成為了MQTT資訊的釋出者,而汽車則成為了MQTT資訊的訂閱者(接收者)。
是以,針對不同的主題,MQTT用戶端可以切換自己的角色。它們可能對主題A來說是資訊釋出者,但是對于主題B就成了資訊訂閱者。
3. MQTT 釋出/訂閱 特性
從上述列子可以看出,MQTT通訊的核心樞紐是MQTT服務端。有了服務端對MQTT資訊的接收、儲存、處理和發送,用戶端在釋出和訂閱資訊時,可以互相獨立,且在空間上可以分離,時間上可以異步。
- 互相獨立
MQTT用戶端是一個個獨立的個體。它們無需了解彼此的存在,依然可以實作資訊交流。
比如以上執行個體中汽車用戶端在釋出“汽車速度”資訊時,汽車用戶端本身可以完全不知道有多少個MQTT用戶端訂閱了“汽車速度”這一主題。而訂閱了“汽車速度”主題的手機和電腦用戶端也完全不知道彼此的存在。
大家隻要訂閱了“汽車速度”主題,MQTT服務端就會在每次收到新資訊時,将資訊發送給訂閱了“汽車速度”主題的用戶端。
- 空間可分離
MQTT用戶端在通訊時必要條件是連接配接到了同一個MQTT通訊網絡。這個網絡可以是網際網路或者區域網路。隻要用戶端聯網,無論在哪裡,都可以實作彼此間的通訊交流。
- 時間可異步
MQTT用戶端在發送和接收資訊時無需同步。這一特點對物聯網裝置尤為重要。有時物聯網裝置會發生意外離線的情況。比如當我們的汽車在行駛過程中,可能會突然進入隧道,這時汽車可能會斷開與MQTT服務端的連接配接。
假設在此時我們的手機用戶端向汽車用戶端所訂閱的“空調溫度”主題釋出了資訊,而汽車恰恰不線上。這時,MQTT服務端可以将“空調溫度”主題的新資訊儲存,待汽車再次上線後,服務端再将“空調溫度”資訊推送給汽車。
4. 會話
從用戶端向服務端發起 MQTT 連接配接請求開始,到連接配接中斷,直到會話過期為止的消息收發序列稱之為會話。
是以,會話可能僅持續一個網絡連接配接,如果用戶端能在會話過期之前重建立立了連接配接的話,會話也可能跨越多個網絡連接配接存在。
會話狀态的使用
如果用戶端因為網絡波動等原因導緻連接配接短暫中斷,但在會話過期前重新與服務端建立了連接配接,那麼就可以沿用上次連接配接建立的訂閱關系,不需要重新訂閱一遍。
在低帶寬、不穩定的網絡場景下,網絡中斷可能會發生得很頻繁,儲存會話狀态的方式避免了每次連接配接都需要重新訂閱,降低了重連時用戶端和服務端的資源消耗。
服務端在用戶端脫機期間為其保留未完成确認的以及後續到達的消息,用戶端重新連接配接時再一并轉發,既可以避免消息丢失,也能夠降低某些場景下使用者對網絡變化的感覺度。
5. QoS(服務品質)
一個物聯網系統中有些資訊非常重要,我們需要確定這類重要資訊可以準确無誤的發送和接收。如果有些資訊在傳輸中丢失但是不會影響系統的運作,則相對不那麼重要。
MQTT服務品質(Quality of Service 縮寫 QoS)正是用于告知物聯網系統,哪些資訊是重要資訊需要準确無誤的傳輸,而哪些資訊不那麼重要,即使丢失也沒有問題。
MQTT 設計了 3 個 QoS 等級:
-
:消息最多發 1 次,占用的網絡資源最低。QoS 0
發送端一旦發送完消息後,就完成任務了。發送端不會檢查發出的消息能否被正确接收到。
在網絡環境穩定的情況下,資訊傳輸一般是不會出現問題的。但是在環境不穩定的情況下,可能會在傳輸過程中出現MQTT消息丢失的情況,是以适用于傳輸重要性較低的資訊。
-
:消息最少發 1 次,但是有可能出現接收端反複接收同一消息的情況。QoS 1
發送端在消息發送完成後,會檢查接收端是否已經成功接收到了消息。
發送端将消息發送給接收端後,會等待接收端的确認。接收端成功接收消息後,會發送一條确認封包
PUBACK
給發送端。如果發送端收到了這條
PUBACK
确認封包,那麼它就知道消息已經成功接收。
假如過了一段時間後,發送端沒有收到
PUBACK
封包,那麼發送端會再次發送消息,然後再次等待接收端的PUBACK确認封包。是以,發送端在沒有收到接收端的
PUBACK
确認封包以前,會重複發送同一條消息。
當發送端重複發送一條消息時,PUBLISH封包中的
dupFlag
會被設定為
True
,為了告訴接收端,此消息為重複發送的消息。
- QoS 2 :消息保證收 1 次。
MQTT協定可以確定接收端隻接收一次消息。但是收發過程相對更加複雜,發送端需要接收端進行兩次消息确認。是以,
QoS 2
最安全的服務級别,也是最慢的服務級别。此類服務等級适用于重要消息傳輸。
- 發送端發送QoS 2的PUBLISH封包給接收端(給你發消息啦)
- 接收端回複PUBREC确認封包給發送端(我收到啦)
- 發送端收到PUBREC封包後,會把此封包進行存儲,并且傳回PUBREL封包作為應答(本次發送可以結束啦)
- 接收端收到PUBREL封包後,會應答發送端一條PUBCOMP封包(可以,結束吧)
Tips:由于QoS1和QoS2都能確定用戶端接收到消息,但是QoS1所占用的資源較QoS2占用資源更小。是以建議使用QoS1來實作網絡資源較為珍貴的環境下傳輸重要資訊。
服務品質降級
對于釋出和訂閱消息的用戶端,服務端會主動采用較低級别的QoS來實作消息傳輸。
場景1:
假如
用戶端A
釋出到
主題1
的消息是采用
QoS = 2
,然而
用戶端B
訂閱
主題1
采用
QoS = 1
。
在這種情況下,服務端會使用較低級别來提供服務。
雖然
A
發送到
主題1
的消息采用
QoS為2
,但是由于
B
在訂閱
主題1
時采用的
QoS為1
,是以
服務端
發送
主題1
的消息給
B
時,采用的
QoS為1
。
場景2:
假如
用戶端A
釋出
主題1
消息時使用
QoS為0
,而
用戶端B
訂閱
主題1
消息時使用`QoS為1。
雖然
用戶端B
訂閱
主題1
消息時
QoS為1
,但是由于
用戶端A
發送
主題1
消息時
QoS為0
,是以
服務端
發送消息給
B
的
QoS為0
。
6. 保留消息
有個這樣的場景:
一套智能家居物聯網系統,有一個檢測室溫的MQTT用戶端,每整點時把目前室溫向MQTT服務端釋出。
系統中還有另一個用戶端用于顯示溫度資訊,用戶端一啟動就會訂閱室溫主題。
正常情況下:假如上午7:00,室溫檢測用戶端将最新的室溫消息釋出到了服務端,那麼訂閱了室溫消息的顯示用戶端也就馬上擷取到室溫消息并且顯示在螢幕上。
但是,7點10分的時候,顯示用戶端的插頭被碰掉了,手動恢複通電後,用戶端啟動後會立刻訂閱室溫主題。
問題來了,室溫用戶端每到整點才釋出一次溫度資訊。上一次釋出時間是
7:00
,下一次釋出時間是
8:00
。是以,盡管顯示用戶端訂閱了室溫主題,它還要等到
8:00
鐘才能收到最新室溫消息。在
8:00
前的幾十分鐘裡,顯示用戶端無法獲知目前室溫資訊。
為了避免以上情況出現,可以讓室溫測量用戶端在每次向室溫主題釋出消息時都使用
保留消息
這一模式将溫度資訊釋出到服務端。這樣無論顯示用戶端在任何時間訂閱室溫主題,都會馬上收到該主題中的
保留消息
。
7. 心跳機制
用戶端在沒有向服務端發送資訊時,可以定時向服務端發送一條消息。這條用于心跳機制的消息也被稱作心跳請求(PINGREQ)。
心跳請求的作用正是**用于告知服務端,目前用戶端依然線上 **。
服務端在收到用戶端的心跳請求後,會回複一條消息。這條回複消息被稱作心跳響應(PINGRESP)。
心跳時間間隔
我們在開發用戶端時,可以對其進行設定。
設定好心跳時間間隔後,用戶端就知道多久要發送一條心跳請求給服務端。當用戶端連接配接服務端時,會将心跳時間間隔資訊放入
CONNECT
封包中的
keepAlive
。
圖中
keepAlive
數值為
60
。這就意味着,用戶端的心跳間隔時間是
60秒
。
- 用戶端在心跳間隔時間内,如果有消息釋出,那就直接釋出消息而不釋出心跳請求。
- 用戶端沒有消息釋出,那麼它就會釋出一條心跳請求給服務端。
用戶端掉線
另外,在實際運作中,如果服務端沒有在
1.5倍心跳時間間隔内
收到用戶端釋出消息(PUBLISH)或發來心跳請求(PINGREQ),那麼服務端就會認為這個
用戶端已經掉線
。
小結一下,心跳機制可以讓服務端随時掌握用戶端連接配接情況。當用戶端“心跳”正常時,服務端即知道用戶端仍然
線上(活着)
。當心跳一旦停止,服務端就會發現該用戶端已經
斷線(死亡)
。
8. 遺囑機制
為了讓用戶端可以更好的發揮作用,便于服務端管理,MQTT 協定允許用戶端在“活着”的時候就寫好遺囑,這樣一旦用戶端
意外斷線
,服務端就可以将用戶端的遺囑公之于衆。
注意:用戶端的遺囑隻在意外斷線時才會釋出,如果用戶端正常的斷開了與服務端的連接配接,這個遺囑機制是不會啟動的,服務端也不會将用戶端的遺囑公布。
意外斷線包括但不限于:
- 因網絡故障或網絡波動,裝置在保持連接配接周期内未能通訊,連接配接被服務端關閉
- 裝置意外掉電
- 裝置嘗試進行不被允許的操作而被服務端關閉連接配接,例如訂閱自身權限以外的主題等
9. 資料包結構
整體由3個部分組成:
-
:存在于所有MQTT資料包中,表示資料包類型及資料包的分組類辨別;固定報頭 Fixed header
-
:存在于部分MQTT資料包中,資料包類型決定了可變頭是否可變報頭 Variable header
整體 MQTT 的消息格式如下圖所示: