MQTT介紹
MQTT(MQ Telemetry Transport)協(xié)議,是 IBM 公司在 1999 年開發(fā)的輕量級網(wǎng)絡(luò)協(xié)議,采用的是發(fā)布 - 訂閱通信模式,它有三個主要特點:
- 采用二進(jìn)制的消息內(nèi)容編碼格式,所以二進(jìn)制數(shù)據(jù)、JSON 和圖片等負(fù)載內(nèi)容都可以方便傳輸。
- 協(xié)議頭很緊湊,協(xié)議交互也簡單,保證了網(wǎng)絡(luò)傳輸流量很小。
- 支持 3 種 QoS(Quality of Service,服務(wù)質(zhì)量)級別,便于應(yīng)用根據(jù)不同的場景需求靈活選擇。
MQTT 協(xié)議非常適合計算能力有限、網(wǎng)絡(luò)帶寬低、信號不穩(wěn)定的遠(yuǎn)程設(shè)備,所以它成為了物聯(lián)網(wǎng)系統(tǒng)事實上的網(wǎng)絡(luò)協(xié)議標(biāo)準(zhǔn)。
MQTT 自身的“基因”很強(qiáng)大
MQTT 協(xié)議作為物聯(lián)網(wǎng)設(shè)備的“第一語言”,不僅是因為 MQTT 的生態(tài)完善,MQTT 協(xié)議本身的優(yōu)秀設(shè)計也是重要的因素。
契合物聯(lián)網(wǎng)大部分應(yīng)用場景的發(fā)布 - 訂閱模式。
采用了發(fā)布 - 訂閱模式,MQTT 協(xié)議具有很多優(yōu)點,比如能讓一個傳感器數(shù)據(jù)觸發(fā)一系列動作;網(wǎng)絡(luò)不穩(wěn)定造成的臨時離線不會影響工作;方便根據(jù)需求動態(tài)調(diào)整系統(tǒng)規(guī)模等。這使得它能滿足絕大部分物聯(lián)網(wǎng)場景的需求。
能夠滿足物聯(lián)網(wǎng)中資源受限設(shè)備需要的輕量級特性。
MQTT 是一個輕量級的網(wǎng)絡(luò)協(xié)議,這一點也是它在物聯(lián)網(wǎng)系統(tǒng)中流行的重要原因。畢竟物聯(lián)網(wǎng)中大量的都是計算資源有限、網(wǎng)絡(luò)帶寬低的設(shè)備。這種“輕量級”體現(xiàn)在兩個方面。
一方面,MQTT 消息采用二進(jìn)制的編碼格式,而不是 HTTP 協(xié)議那樣的文本的表述方式。在 HTTP 協(xié)議傳輸?shù)倪@段文本中,每個字符都要占用 1 個字節(jié)。而如果使用 MQTT 協(xié)議,一個字節(jié)就可以表示很多內(nèi)容。下面的圖片展示了 MQTT 的固定頭的格式,這個固定頭只有 2 個字節(jié):

第一個字節(jié)分成了高 4 位(4~7)和低 4 位(0~3);低 4 位是數(shù)據(jù)包標(biāo)識位,其中的每一比特位又可以表示不同的含義;高 4 位是不同數(shù)據(jù)包類型的標(biāo)識位。第二個字節(jié)表示數(shù)據(jù)包頭部和消息體的字節(jié)共個數(shù),其中最高位表示有沒有第三字節(jié)存在,來和第二個字節(jié)一起表示字節(jié)共個數(shù)。如果有第三個字節(jié),那它的最高位表示是否有第四個字節(jié),來和第二個字節(jié)、第三個字節(jié)一起表示字節(jié)總個數(shù)。依此類推,還可能有第四個字節(jié)、第五個字節(jié),不過這個表示可變頭部和消息體的字節(jié)個數(shù)的部分,最多也只能到第五個字節(jié),所以可以表示的最大數(shù)據(jù)包長度有 256MB。
比如,一個請求建立連接的 CONNECT 類型數(shù)據(jù)包,頭部需要 14 個字節(jié);發(fā)布消息的 PUBLISH 類型數(shù)據(jù)包頭部只有 2~4 個字節(jié)。
另一方面,體現(xiàn)在消息的具體交互流程設(shè)計非常簡單,所以 MQTT 的交互消息類型也非常少。

從表格可以看出,MQTT 3.1.1 版本一共定義了 14 種數(shù)據(jù)包的類型,在第一個字節(jié)的高 4 位中分別對應(yīng)從 1 到 14 的數(shù)值。
時刻關(guān)注物聯(lián)網(wǎng)設(shè)備低功耗需求的優(yōu)化設(shè)計。
除了讓協(xié)議足夠輕量,MQTT 協(xié)議還很注重低功耗的優(yōu)化設(shè)計,這主要體現(xiàn)在對能耗和通信次數(shù)的優(yōu)化。
比如,MQTT 協(xié)議有一個 Keepalive 機(jī)制。它的作用是,在 Client 和 Broker 的連接中斷時,讓雙方能及時發(fā)現(xiàn),并重新建立 MQTT 連接,保證主題消息的可靠傳輸。這個機(jī)制工作的原理是:Client 和 Broker 都基于 Keepalive 確定的時間長度,來判斷一段時間內(nèi)是否有消息在雙方之間傳輸。這個 Keepalive 時間長度是在 Client 建立連接時設(shè)置的,如果超出這個時間長度,雙方?jīng)]有收到新的數(shù)據(jù)包,那么就判定連接斷開。
雖然 Keepalive 要求一段時間內(nèi)必須有數(shù)據(jù)包傳輸,但實際情況是,Client 和 Broker 不可能時時刻刻都在傳輸主題消息,這要怎么辦呢?
MQTT 協(xié)議的解決方案是,定義了 PINGREQ 和 PINGRESP 這兩種消息類型。它們都沒有可變頭部和消息體,也就是說都只有 2 個字節(jié)大小。Client 和 Broker 通過分別發(fā)送 PINGREQ 和 PINGRESP 消息,就能夠滿足 Keepalive 機(jī)制的要求。我猜你也想到了,如果要一直這樣“傻傻地”定期發(fā)送消息,那也太浪費電量和網(wǎng)絡(luò)資源了。所以,如果在 Keepalive 時間長度內(nèi),Client 和 Broker 之間有數(shù)據(jù)傳輸,那么 Keepalive 機(jī)制也會將其計算在內(nèi),這樣就不需要再通過發(fā)送 PINGREQ 和 PINGRESP 消息來判斷了。
除了 Keepalive 機(jī)制,MQTT 5.0 中的重復(fù)主題特性也能幫助我們節(jié)省網(wǎng)絡(luò)資源。Client 在重復(fù)發(fā)送一個主題的消息時,可以從第二次開始,將主題名長度設(shè)置為 0,這樣 Broker 會自動按照上次的主題來處理消息。這種情況對傳感器設(shè)備來說十分常見,所以這個特性在工作中很有實際意義。
針對物聯(lián)網(wǎng)中多變的網(wǎng)絡(luò)環(huán)境提供的多種服務(wù)質(zhì)量等級。
MQTT 協(xié)議設(shè)計了 3 種不同的 QoS (Quality of Service,服務(wù)質(zhì)量)級別。你可以根據(jù)場景靈活選擇,在不同環(huán)境下保證通信是可靠的。
什么是 QoS?它是指通信雙方關(guān)于消息傳送可靠程度的協(xié)商。
QoS 0級別,消息只發(fā)送一次,消息可能丟失;
QoS 1級別 ,發(fā)送方會接收反饋,保證消息的送達(dá),但是可能消息會重復(fù)。
QoS 2 級別,通過發(fā)送方和接收方的多次交互,保證消息有且只有一次。

可以看到,QoS 0 和 QoS 1 的流程相對比較簡單;而 QoS 2 為了保證有且只有一次的可靠傳輸,流程相對復(fù)雜些。正常情況下,QoS 2 有 PUBLISH、PUBREC、PUBREL 和 PUBCOMP 4 次交互。至于“不正常的情況”,發(fā)送方就需要重復(fù)發(fā)送消息。比如一段時間內(nèi)沒有收到 PUBREC 消息,就需要再次發(fā)送 PUBLISH 消息。不過要注意,這時要把消息中的 “重復(fù)”標(biāo)識設(shè)置為 1,以便接收方能正確處理。同樣地,如果沒有收到 PUBCOMP 消息,發(fā)送方就需要再次發(fā)送 PUBREL 消息。
支持在物聯(lián)網(wǎng)應(yīng)用中越來越被重視的數(shù)據(jù)安全。
說到安全傳輸,首先我們需要驗證 Client 是否有權(quán)限接入 MQTT Broker。為了控制 Client 的接入,MQTT 提供了用戶名 / 密碼的機(jī)制。在建立連接過程中,它可以通過判斷用戶名和密碼的正確性,來篩選有效連接請求。但是光靠這個機(jī)制,還不能保證網(wǎng)絡(luò)通信過程中的數(shù)據(jù)安全。因為在明文傳輸?shù)姆绞较?,不止設(shè)備數(shù)據(jù),甚至用戶名和密碼都可能被其他人從網(wǎng)絡(luò)上截獲而導(dǎo)致泄漏,于是其他人就可以偽裝成合法的設(shè)備發(fā)送數(shù)據(jù)。所以,我們還需要通信加密技術(shù)的支持。MQTT 協(xié)議支持 SSL/TLS 加密通信方式。采用 SSL/TLS 加密之后,MQTT 將轉(zhuǎn)換為 MQTTS。這有點類似于 HTTP 和 HTTPS 的關(guān)系。
體驗 MQTT
使用hbmqtt簡單體驗MQTT
第一步是安裝 hbmqtt,它是一個開源的基于 Python 語言的 MQTT Broker 軟件,正好包括我們需要使用一些工具。跟其他選擇相比,這個軟件安裝起來非常方便,因為它在 Python 的 PYPI 軟件倉庫中就有,所以你通過 pip 命令就可以安裝。這也是選擇使用它的主要原因。不過要注意的是,hbmqtt 是基于 Python3 實現(xiàn)的,因此這里使用的是 pip3 工具。
pip3 install hbmqtt
安裝完成后,我們就可以使用 hbmqtt 中提供的 hbmqtt_sub 和 hbmqtt_pub 這兩個命令行工具了。通過名字,你應(yīng)該也可以看出 hbmqtt_sub 可以充當(dāng)訂閱者的角色;hbmqtt_pub 可以作為消息的發(fā)布者。
至于訂閱者和發(fā)布者之間的經(jīng)紀(jì)人,也就是 MQTT Broker,我們使用 Eclipse 免費開放的在線 Broker 服務(wù)。打開鏈接,你可以看到關(guān)于端口的介紹信息,加密和非加密方式都支持,而且還有基于 Websocket 的實現(xiàn),這對基于前端網(wǎng)頁的應(yīng)用來說是非常有利的。
我們先使用 1883 端口的非加密方式,然后為消息傳輸確定一個主題(Topic)。主題確定了消息的類別,用于消息過濾。比如我們待會兒要測試的消息可以設(shè)為“/mqtt/test”。
接著,我們在電腦的終端界面輸入下面的命令,就可以訂閱這個主題消息:
hbmqtt_sub --url mqtt://test.mosquitto.org:1883 -t /mqtt/test
現(xiàn)在,我們啟動另外一個終端界面,通過 hbmqtt_pub 發(fā)布一個 “/mqtt/test” 主題的消息:
hbmqtt_pub --url mqtt://test.mosquitto.org:1883 -t /mqtt/test -m Hello,World!
搭建自己的MQTT Broker,進(jìn)行深度體驗
使用VerneMQ作為MQTT Broker
第一步,使用docker安裝VerneMQ。
docker run -p 1883:1883 -e "DOCKER_VERNEMQ_ACCEPT_EULA=yes" -e "DOCKER_VERNEMQ_ALLOW_ANONYMOUS=on" --name vernemqtt -d vernemq/vernemq
第二步,使用VSCode連接VerneMQ,安裝VsMqtt插件。

MQTT 的客戶端(Client)代碼實現(xiàn),可以使用Eclipse Paho 項目。