學習MQTT協議。如果只是看了相關文檔就認為可以了。那是一個錯誤的觀念。筆者為了能更好的去理解MQTT協議。看了不少相關的開源Broker的項目。可惜這些項目一般都是不完全的。不過從這些項目中筆者至少發現他們大部都是通過Netty這個通信框架來完成的。哪怕是大型項目ActiveMQ也脫不了俗。特別是商用HiveMQ更是列為重要的一部分。所以筆者接下來會用Netty框架來實現一些代碼。這樣子有助於我們去理解MQTT協議。
本節筆者會來講連接報文(CONNECT)。可以說他是所有報文的基礎。所有的動作都必須在連接之上操作。我們都知道MQTT是基於TCP/IP網絡協議的。並以字節流傳輸的。他的行為動作更為簡單。如下
我們可以知道連接會用到倆個報文類型類——CONNECT報文和CONNACK報文。其中CONNECT報文比較復雜一點。可以說是所有報文中信息種類最多的。CONNACK報文的最大特點就是沒有有效載荷部分。
接下筆者就會講解一下連接的相關行為。同時也希望讀者們記住筆者這里講的一般是MQTT 3.1 和MQTT 3.1.1的協議。
CONNECT報文就是相當連接請求一樣子。所以當客戶端和服務端建立之后,服務端接受的第一份報文就必須是連接報文。相信這個不用筆者說明也知道為什么。同時客戶端這邊要保證連接報文只能發送一次。同時在服務端也要有驗證來自客戶端的連接報文只有一次。如果發送倆次以上的連接報文的話,不好意思請當作違反了協議斷開當前連接。從前面的圖片里面我們就知道如果客戶端發送一個連接報文(CONNECT)之后,服務端就會返回一個連接確定報文(CONNACK)。如果客戶端在一段時間之后,還沒有接收到來自服務端的連接確定報文(CONNACK)的話,客戶端一定要斷開連接。同時要重起一個新的網絡連接。在發一次連接報文(CONNECT)。
我們都知道控制報文分為固定報頭+可變報頭+有效載荷部分。其中可變報頭和有效載荷並不是必須要擁有的。而CONNECT報文卻是所有中筆者認為最為復雜的報文。不管是可變報頭還是有效載荷部分他都擁有。
固定報頭
固定報頭的結構上一單已講過了。占八個字節。從上一單的報文類型列表我們知道他的值為1。報文類型占四個字節。所以他的二進制就是0001。在MQTT 3.1里面有講到DUP、QoS、 RETAIN 都沒有被用到。這些加起來占四個字節。這樣子的話筆者以為固定報頭最終的二進制是0001。但是在MQTT 3.1.1里面卻又說到以0保留了。所以最終二進制是00010000。簡單的講DUP、QoS、 RETAIN雖然沒有被用到,所以設置為0。
可變報頭
連接報文(CONNECT)的可變報頭的結果構分為四個部分:協議名(Protocol Name)、協議等級(Protocol Level)、連接標志(Connect Flags)、保持連接(Keep Alive)。其實協議名(Protocol Name)和協議等級(Protocol Level)筆者也不是很明白有什么用。只是知道MQTT 3.1 的協議名是MQIsdp,協議等級是3。而MQTT 3.1.1的協議名卻是MQTT,等級是4。好吧。至少說明不同版本的MQTT協議存在不同的協議名和協議等級。這里面在文檔也有一個動作存在。就是如果服務端發現協議名不正確的話,就必須斷開連接。如果是協議等級不對話,就是必須回返一個碼。
要問可變報頭里面最重要的部分是哪一部分的話,筆者認為是連接標志(Connect Flags)。連接標志里面包括用戶名標志(User Name Flag)、密碼標志(Password Flag)、遺囑保留(Will Retain)、清理會話(Clean Session)、保留(Reserved)等。他們的位置如圖下。
清理會話(CleanSession)
客戶端和服務端之間的通信時間,筆者認為是一次會話。也就是在連接成功的時候為會話開始。當斷開連接的時候表示一次會話結束了。即然是會話,自然就有會話狀態。這些狀態當前就是指通信之間的信息。如下
一、服務端的會話狀態:
1)客戶端的訂閱信息。
2)已經發送給客戶端,但是還沒有完成確認的QoS 1和QoS 2級別的消息。
3)即將傳輸給客戶端的QoS 1和QoS 2級別的消息
4)已從客戶端接收,但是還沒有完成確認的QoS 2級別的消息
5)准備發送給客戶端的QoS 0級別的消息(可選)
二、客戶端的會話狀態:
1)已經發送給服務端,但是還沒有完成確認的QoS 1和QoS 2級別的消息
2)已從服務端接收,但是還沒有完成確認的QoS 2級別的消息
知道了會話狀態,就可以明白清理會話就用來表示是否清除或是保存會話狀態。如果清理會話為0的話,表示要保存這里會話狀態。如果連接斷開了,在次連接的時候,服務要以什么來獲得已存在的會話狀態呢?客戶ID。這是在有效載荷分部分的下面會講到。根據客戶ID來找查有沒有相關的會話狀態存在。如果存在,就必須以存在的會話狀態來建立連接。如果沒有就創建一個新會話狀態。當然斷開之后,不管是客戶端還是服務端都要保存當前會話狀態。這個舉動筆者認為是為了數據重用吧。也可以說是保證數據不會丟失。如果清理會話為1的話,事情就變的很簡單。給我清除掉就是行了。沒有一次連接都是一個新的會話。
遺囑標志(Will Flag)
關看到遺囑倆個字就應該明白。用於表示在網絡連接突關閉的時候,要不要發送遺囑。遺囑標志可關系遺囑QoS(Will QoS),遺囑保留(Will Retain),還有有效載荷里面的遺囑主題(Will Topic)和遺囑消息(Will Message)。可以說遺囑標志是遺囑功能總開關。只當遺囑標志為1的時候。說明要用到遺囑功能。那遺囑QoS(Will QoS),遺囑保留(Will Retain)就可以被啟用。也就是說遺囑主題(Will Topic)和遺囑消息(Will Message)必須要在有效載荷部分里面出現。
遺囑QoS(Will QoS)
他的值主要要看遺囑標志(Will Flag)是的值。如果遺囑標志(Will Flag)是1的話,遺囑QoS可以是0,1,2。這些值表示跟服務質量是一樣子。如果遺囑標志(Will Flag)是0的話,那么遺囑QoS必須也是0;
遺囑保留(Will Retain)
他的值也是要看遺囑標志(Will Flag)是的值。如果遺囑標志(Will Flag)是1的話,遺囑保留可以是0或1。用於表示有沒有做於保留消息發布;
用戶名標志 User Name Flag 和 密碼標志 Password Flag
筆者為什么把這倆個放在一起呢?相信做過很多業務開發的人都知道用戶名和密碼。上面這倆個就是用於標示在有效載荷里有沒有相關的部分。比如。當就用戶名標示為1的時候,那就是說明有效載荷部分里面有用戶名的信息。同理密碼標示為1 就是表示有效載荷部分有密碼的信息。這里有一點要注意就是如果用戶名標志為0的話,那密碼標志就必須為0。有用戶名和密碼的話,我們就可以在服務端做一些身分驗證的業務。同時還可以加入一些權限。
上面筆者講過可變報頭分為四個部分。剩下一個保持連接(Keep Alive)。保持連接(Keep Alive)表示在客戶端一個報文發送結束之后到下一次報文發送之前的空閑時間。單位為秒。記住是在客戶端而不是服務端。如果在保持連接的時間內沒有發送任何報文的話。文檔里面是要求發送一個PINGREQ報文。通過他判斷服務端和客戶端之間的連接狀態。如果在一時間段接受不到沒有收到服務端發來PINGRESP報文,那么就應該關閉於服務端的連接。前面講都是在客戶端這邊要做的事情。服務端這當然也不能少。如果服務端判斷保持連接不為0的時候,就要以保持連接值的1.5陪時間來判斷是否有接到報文。如果沒有接受報文的話,就要關閉跟客戶端之間的連接。值得注意的事。服務端要斷開客戶端不是根據保持連接來處理。而是查看這個客戶端是不是閑了。如果是的話,什么時候都可以斷開連接的。
有效載荷
有效載荷事實上就是通信里面的用戶要存放的信息。只是這里面又不能全是用戶信息。還包括了一些MQTT需要的信息。這跟可變報頭的一些標志有關系。但是不管什么樣子。客戶ID是必須在第一位的。后面就是遺囑主題和遺囑消息。最后才是用戶和密碼。
有效載荷 = 客戶ID + 遺囑主題 + 遺囑消息 + 用戶 + 密碼
為了方便學習,筆者用軟件把包搞下來。上一章也講到過什么樣子搞下。如下圖下
上面的圖片算是比較全的連接報文。筆者也根據相應的標示出他們所在的位置。圖片下方是報文相應的二進制流。紅線標出的數據是報文真時的數據對應。而綠色線表示是接下來紅線相關數據的長度。相信如果你看過MQTT相關的文檔就應該知道MSB和LSB關鍵字的作用。那么圖片1紅線就是固定報頭,2紅線就是可變報頭。從3紅線開始后面都是有效載荷部分。
通上面我們大至能了解MQTT的連接過程要做些什么。在MQTT文檔里面有一點筆者有一點吃驚。筆者以為如果一個客戶端發送連接報文(CONNECT)之后,並接受到了服務端的連接報文確定(CONNACK)之后,才可以有進行發布相關信息。可是MQTT文檔指出客戶端在發送連接報文(CONNECT)之后就可以進行發布了。而不需要等待連接報文確定(CONNACK)。如果服務端因為客戶端不合適,可以完全不需要去處理和反應之前送來的消息。
連接報文確定(CONNACK)的特點就是沒有有效載荷的部分。客戶端想要知道自己有沒有連接成功服務端。就必須去查看可變報頭里面的回返碼。在CONNACK報文的可變報頭里面還有一叫Session Present。他用於表示服務端沒有保存會話狀態。這個筆者就不多講。各位自己看下圖吧。
注意:紅線為Session Present,綠線為返回碼。