1. 概述
MQTT(Message Queuing Telemetry Transport,消息隊列遙測傳輸協議),是一種基於發布/訂閱(Publish/Subscribe)模式的輕量級通訊協議,該協議構建於TCP/IP協議上,由IBM在1999年發布,目前最新版本為v3.1.1。MQTT最大的優點在於可以以極少的代碼和有限的帶寬,為遠程設備提供實時可靠的消息服務。做為一種低開銷、低帶寬占用的即時通訊協議,MQTT在物聯網、小型設備、移動應用等方面有廣泛的應用。眾所周知,TCP/IP參考模型可以分為四層:應用層、傳輸層、網絡層、鏈路層。TCP和UDP位於傳輸層,應用層常見的協議有HTTP、FTP、SSH等。MQTT協議運行於TCP之上,屬於應用層協議,因此只要是支持TCP/IP協議棧的地方,都可以使用MQTT。
2. MQTT客戶端
一個使用MQTT協議的應用程序或者設備,它總是建立到服務器的網絡連接。客戶端可以:
(1)發布其他客戶端可能會訂閱的信息; //發布消息
(2)訂閱其它客戶端發布的消息; //訂閱消息
(3)退訂或刪除應用程序的消息; //退訂消息
(4)斷開與服務器連接。 //斷開,連接服務器
3. MQTT服務器
MQTT服務器以稱為“消息代理”(Broker),可以是一個應用程序或一台設備。它是位於消息發布者和訂閱者之間,它可以:
(1)接受來自客戶的網絡連接; //接受客戶端連接
(2)接受客戶發布的應用信息; //接收客戶端發布的消息
(3)處理來自客戶端的訂閱和退訂請求; //處理消息的訂閱及退訂
(4)向訂閱的客戶轉發應用程序消息。 //推送消息
4. MQTT消息格式
每條MQTT命令消息的消息頭都包含一個固定的報頭,有些消息會攜帶一個可變報文頭和一個負荷。消息格式如下:
固定報文頭 | 可變報文頭 | 負荷
4.1 固定報文頭(Fixed Header)
MQTT固定報文頭最少有兩個字節,第一字節包含消息類型(Message Type)和QoS級別等標志位。第二字節開始是剩余長度字段,該長度是后面的可變報文頭加消息負載的總長度,不包括用於編碼剩余長度字段本身的字節數,該字段最多允許四個字節。



4.2 可變報文頭(Variable Header)
可變報文頭主要包含協議名、協議版本、連接標志(Connect Flags)、心跳間隔時間(Keep Alive timer)、連接返回碼(Connect Return Code)、主題名(Topic Name)等,后面會針對主要部分進行講解。

4.2.1 消息質量(QoS)
MQTT消息質量有三個等級,QoS 0,QoS 1和 QoS 2。
QoS 0:最多分發一次。消息的傳遞完全依賴底層的TCP/IP網絡,協議里沒有定義應答和重試,消息要么只會到達服務端一次,要么根本沒有到達。
QoS 1:至少分發一次。服務器的消息接收由PUBACK消息進行確認,如果通信鏈路或發送設備異常,或者指定時間內沒有收到確認消息,發送端會重發這條在消息頭中設置了DUP位的消息。
QoS 2:只分發一次。這是最高級別的消息傳遞,消息丟失和重復都是不可接受的,使用這個服務質量等級會有額外的開銷。
4.2.2 遺願標志(Will Flag)
在可變報文頭的連接標志位字段(Connect Flags)里有三個Will標志位:Will Flag、Will QoS和Will Retain Flag,這些Will字段用於監控客戶端與服務器之間的連接狀況。如果設置了Will Flag,就必須設置Will QoS和Will Retain標志位,消息主體中也必須有Will Topic和Will Message字段。
那遺願消息是怎么回事呢?服務器與客戶端通信時,當遇到異常或客戶端心跳超時的情況,MQTT服務器會替客戶端發布一個Will消息。當然如果服務器收到來自客戶端的DISCONNECT消息,則不會觸發Will消息的發送。
因此,Will字段可以應用於設備掉線后需要通知用戶的場景。
4.2.3 連接保活心跳機制(Keep Alive Timer)
MQTT客戶端可以設置一個心跳間隔時間(Keep Alive Timer),表示在每個心跳間隔時間內發送一條消息。如果在這個時間周期內,沒有業務數據相關的消息,客戶端會發一個PINGREQ消息,相應的,服務器會返回一個PINGRESP消息進行確認。如果服務器在一個半(1.5)心跳間隔時間周期內沒有收到來自客戶端的消息,就會斷開與客戶端的連接。心跳間隔時間最大值大約可以設置為18個小時,0值意味着客戶端不斷開
4.3 有效負荷(Payload)
Payload直譯為負荷,可能讓人摸不着頭腦,實際上可以理解為消息主體(body)。

當MQTT發送的消息類型是CONNECT(連接)、PUBLISH(發布)、SUBSCRIBE(訂閱)、SUBACK(訂閱確認)、UNSUBSCRIBE(取消訂閱)時,則會帶有負荷。
(1)CONNECT,消息體內容主要是:客戶端的ClientID、訂閱的Topic、Message以及用戶名和密碼。
(2)SUBSCRIBE,消息體內容是一系列的要訂閱的主題以及QoS。
(3)SUBACK,消息體內容是服務器對於SUBSCRIBE所申請的主題及QoS進行確認和回復。
(4)UNSUBSCRIBE,消息體內容是要訂閱的主題。
(5)PUBLISH,消息體內容是相關主題的數據。
5. MQTT控制報文
介紹MQTT協議的報文組成並通過wireshark抓取報文包分析報文內容
5.1 連接服務端(CONNECT)
Connect報文在MQTT客戶端連接服務器時發出,報文由三部分組成,下面將分別介紹
5.1.1 固定報頭(fixed header)

Connect名稱的值為1,低四位保留,剩余長度保存可變報頭(10字節)加上有效載荷的長度。
5.1.2 可變報頭(variable header)
Connect報文的可變報頭包含協議名,協議等級,連接標志和保持連接

報文協議名之前有兩個字節的報文標識符,唯一標識這條報文。
連接標志包含用戶名標志(username flag)、密碼標志(password flag)、遺囑標志(will flag)、遺囑服務指令(will Qos)、遺囑保留標志(will retain)、清除會話標志(clean session)、保留位(reserved)。
用戶名標志(username flag):若用戶名標志被置為1,有效載荷中必須包含用戶名字段。
密碼標志(password flag):若密碼標志被置為1,有效載荷中必須包含密碼字段,當用戶標志被置為0時,密碼標志必須被置0.。
遺囑標志(will flag):若遺囑標志被置1,遺囑服務指令(will Qos)與遺囑保留標志(will retain)會被服務器用到,遺囑消息中必須包含will topic和will message。
遺囑服務指令(will Qos):如果遺囑標志被設置為0,遺囑QoS也必須設置為0(0x00),如果遺囑標志被設置為1,遺囑QoS的值可以等於0(0x00),1(0x01),2(0x02)。它的值不能等 於3。
遺囑保留標志(will retain):若遺囑保留標志位被置位,服務器將保留遺囑消息(保留發布),當客戶端異常斷開連接時將遺囑發給訂閱遺囑主題的客戶。
清除會話標志(clean session):標志被設置為1,客戶端和服務端必須丟棄之前的任何會話並開始一個新的會話(之前的訂閱與發布消息被刪除),若標志為0,恢復與服務器會話連接,若沒有連接 新建一個會話連接(不刪除之前與客戶端的會話信息並保存斷開本次會話之后的Qos1與Qos2消息)。
保留位(reserved):如果不為0必須斷開客 戶端連接。
報文最后兩字節為發送心跳包的間隔時間,當客戶端沒有數據發給服務器時,須發送心跳包(pingreq)到服務器,保證連接不斷開。
5.1.3 有效載荷(Payload)
CONNECT報文的有效載荷(payload)包含一個或多個以長度為前綴的字段,可變報頭中的 標志決定是否包含這些字段。如果包含的話,必須按這個順序出現:客戶端標識符,遺囑主題,遺囑消息,用戶名,密碼。
客戶端標識符(client identifier):服務器通過識別客戶標識符,確定客戶端,識別兩者間的MQTT會話相關狀態,服務器允許客戶端提供一個零字節的標識符,但clean session必須置1。

5.2連接請求確認(CONNACK)
Connack為服務器確認客戶端連上服務給出的回應。
5.2.1 固定報頭(fixed header)

5.2.2 可變報頭(variable header)



5.3發布消息(PUBLISH)
發布消息可由客戶端或服務器發出,被消息訂閱者接收。
5.3.1 固定報頭(fixed header)

固定報頭第一字節的低四位分別保存重發標志(DUP),服務質量(Qos),保留標志(RETAIN)。
重發標志(DUP):當該位被置1,表示該條報文為重發報文,客戶端或服務端請求重發一個PUBLISH報文時,必須將DUP標志設置為1。對於QoS 0的消息,DUP標志必須設置為0。
服務質量(Qos):表示發送質量,取值有00 01 10。
保留標志(RETAIN):若該位被置1,表示服務器必須保存其應用消息和質量等級,若為0,表示該消息不須保存,如果服務端收到一條保留(RETAIN)標志為1的QoS 0消息,它必須丟棄之前為那個主題保留的任何消息。它應該將這個新的QoS0消息當作那個主題的新保留消息,但是任何時候都可以選擇丟棄它,保留標志為1且有效載荷為零字節的PUBLISH報文會被服務端當作正常消息處理,它會被發送給訂閱主題匹配的客戶端。此外,同一個主題下任何現存的保留消息必須被移除,因此這個主題之后的任何訂閱者都不會收到一個保留消息。
5.3.2 可變報頭(variable header)
可變報頭按順序包含主題名和報文標識符。
5.3.3 有效載荷(Payload)
若Qos為0,無響應,若Qos為1,返回PUBACK報文,若Qos為2,返回PUBREC報文。

5.4發布確認(PUBACK)
由服務器或客戶端確認已接收到pub消息
5.4.1 固定報頭(fixed header)

5.4.2 可變報頭(variable header)
只有兩字節的報文標識(報文標識為pub報文標識)

5.5發布收到 PUBREC(QoS 2,第一步)
5.5.1 固定報頭(fixed header)

5.5.2 可變報頭(variable header)
只有兩字節的報文標識(報文標識為pub報文標識)

5.6發布釋放 PUBREL(QoS 2,第二步)
5.6.1 固定報頭(fixed header)

5.6.2 可變報頭(variable header)
只有兩字節的報文標識(報文標識為pub報文標識)

5.7發布完成 PUBCOMP(QoS 2,第三步)
5.7.1 固定報頭(fixed header)

5.7.2 可變報頭(variable header)
只有兩字節的報文標識(報文標識為pub報文標識)

5.8訂閱主題(SUBSCRIBE)
客戶端通過訂閱消息的方式來接收服務端下發的消息
5.8.1 固定報頭(fixed header)
SUBSCRIBE控制固定報頭的第3,2,1,0位是保留位,必須分別設置為0,0,1,0。服務端必須將其它的任何值都當做是不合法的並關閉網絡連接

5.8.2 可變報頭(variable header)
只有兩字節的報文標識
5.8.3 有效載荷(Payload)

消息載體包含若干主題過濾器和服務質量等級
PUB時指定的qos是服務器肯定按此規則接收,但是最終訂閱者不一定。
SUB時指定的qos表示訂閱者可以接收的最高消息等級,也就是可能收到更低等級的消息

5.9訂閱確認(SUBACK)
SUBACK報文包含一個返回碼清單,它們指定了SUBSCRIBE請求的每個訂閱被授予的最大QoS等級。
5.9.1 固定報頭(fixed header)

5.9.2 可變報頭(variable header)
只有兩字節的報文標識
5.9.3 有效載荷(Payload)

允許的返回碼為0x00-最大Qos0,0x01-最大Qos1,0x02-最大Qos2,0x80-失敗

5.10取消訂閱 (UNSUBSCRIBE)
5.10.1 固定報頭(fixed header)

5.10.2 可變報頭(variable header)
只有兩字節的報文標識
5.10.3 有效載荷(Payload)
包含需要取消訂閱的主題過濾器的列表.

5.11取消訂閱確認 (UNSUBACK)
5.11.1 固定報頭(fixed header)

5.11.2 可變報頭(variable header)
只有兩字節的報文標識(報文標識為unsub報文標識)

5.12心跳請求 (PINGREQ)
客戶端發送PINGREQ報文給服務端的。用於:
1.在沒有任何其它控制報文從客戶端發給服務的時,告知服務端客戶端還活着。
2.請求服務端發送 響應確認它還活着。
3.使用網絡以確認網絡連接沒有斷開
5.12.1 固定報頭(fixed header)


5.13心跳響應 (PINGRESP)
5.13.1 固定報頭(fixed header)


5.14斷開連接 (DISCONNECT)
5.14.1 固定報頭(fixed header)


抓包注意:用標准不加密MQTT能抓到便於查看的報文,加密報文解析時不便於理解
創作不易,白嫖不好,各位的支持和認可,就是我創作的最大動力,我們下篇文章見!
清風 | 文 【原創】
如果本篇博客有任何錯誤,請批評指教,不勝感激 !
