MQTT 物联网协议
目錄
- MQTT 簡介
- MQTT 協議(上)
- MQTT 通信基本原理
- 連接MQTT 服務端
- CONNECT 請求報文
- CONNACK 回復報文
- 斷開連接
- 發布消息、訂閱主題與取消訂閱主題
- PUBLISH–發布消息報文
- SUBSCRIBE--訂閱主題報文
- UNSUBSCRIBE--取消訂閱主題報文
- 主題的格式
- MQTT 初體驗
- 下載、安裝MQTT.fx 客戶端軟件
- MQTT 服務端
- 動手測試
- 使用手機作為客戶端
- MQTT 協議(下)
- QoS 等級
- 保留消息
- MQTT 的心跳機制
- MQTT 的遺囑機制
- MQTT 用戶密碼認證
- 移植MQTT 客戶端庫
- MQTT 客戶端庫API 介紹
- MQTTClient_message結構體
- 創建客戶端對象
- 連接服務端
- 設置回調函數
- 發布消息
- 訂閱主題和取消訂閱主題
- 斷開服務端連接
- 編寫客戶端程序
- 演示
MQTT 簡介
目前物聯網的通訊協議并沒有一個統一的標準,比較常見的有MQTT、CoAP、DDS、XMPP 等,在這其中,MQTT(消息隊列遙測傳輸協議)應該是應用最廣泛的標準之一。目前,MQTT 已逐漸成為IoT 領域最熱門的協議,阿里云IoT 物聯網平臺使用的就是MQTT協議。
MQTT 協議是為工作在低帶寬、不可靠網絡的遠程傳感器和控制設備之間的通訊而設計的協議,主要的幾項特性:
- ①、使用發布/訂閱消息模式,提供一對多的消息發布,解除應用程序耦合。
- ②、基于TCP/IP 提供網絡連接。主流的MQTT 是基于TCP 連接進行數據推送的,但是同樣也有基于UDP 的版本,叫做MQTT-SN。
- ③、支持QoS 服務質量等級。根據消息的重要性不同設置不同的服務質量等級。
- ④、小型傳輸,開銷很小,協議交換最小化,以降低網絡流量。
嵌入式設備的運算能力和帶寬都相對薄弱,在手機移動應用方面,
MQTT 是一種不錯的Android 消息推送方案。 - ⑤、使用will 遺囑機制來通知客戶端異常斷線。
- ⑥、基于主題發布/訂閱消息,對負載內容屏蔽的消息傳輸。
- ⑦、支持心跳機制。
MQTT 協議(上)
MQTT 通信基本原理
MQTT 是一種基于客戶端-服務端架構的消息傳輸協議
服務端
MQTT 服務端通常是一臺服務器(broker),它是MQTT 信息傳輸的樞紐,負責將MQTT 客戶端發送來的信息傳遞給MQTT 客戶端;MQTT 服務端還負責管理MQTT 客戶端,以確保客戶端之間的通訊順暢,保證MQTT 信息得以正確接收和準確投遞。
客戶端
MQTT 客戶端可以向服務端發布信息,也可以從服務端收取信息;我們把客戶端發送信息的行為稱為“發布”信息。而客戶端要想從服務端收取信息,則首先要向服務端“訂閱”信息。“訂閱”信息這一操作很像我們在使用微信時“關注”了某個公眾號,當公眾號的作者發布新的文章時,微信官方會向關注了該公眾號的所有用戶發送信息,告訴他們有新文章更新了,以便用戶查看。
MQTT 主題
上面我們講到了,客戶端想要從服務器獲取信息,首先需要訂閱信息,那客戶端如何訂閱信息呢?這里我們要引入“主題(Topic)”的概念,“主題”在MQTT 通信中是一個非常重要的概念,客戶端發布信息以及訂閱信息都是圍繞“主題”來進行的,并且MQTT 服務端在管理MQTT 信息時,也是使用“主題”來控制的。
客戶端發布消息時需要為消息指定一個“主題”,表示將消息發布到該主題;而對于訂閱消息的客戶端來說,可通過訂閱“主題”來訂閱消息,這樣當其它客戶端或自己(當前客戶端)向該主題發布消息時,MQTT 服務端就會將該主題的信息發送給該主題的訂閱者(客戶端)。
為了便于您更好理解服務端是如何通過“主題”來控制客戶端之間的信息通訊,我們來看看下圖實例:
在以上圖示中一共有三個MQTT 客戶端,它們分別是開發板、手機和電腦。MQTT 服務端在管理MQTT通信時使用了“主題”來對信息進行管理。比如上圖所示,假設我們需要利用手機和電腦獲取開發板在運行過程中SoC 芯片的溫度,那么首先電腦和手機這兩個客戶端需要向MQTT 服務器訂閱主題“芯片溫度”;接下來,當開發板客戶端向服務端的“芯片溫度”主題發布信息(假設信息的內容就是當前的溫度值)后,服務端就會首先檢查都有哪些客戶端訂閱了“芯片溫度”這一主題的信息,而當它發現訂閱了該主題的客戶端有一個手機和一個電腦,于是服務端就會將剛剛收到的“芯片溫度”信息轉發給訂閱了該主題的手機和電腦客戶端。
通過以上的這種實例,手機和電腦便可以獲取到開發板運行時SoC 芯片的溫度值。開發板是“芯片溫度”主題的發布者,而手機和電腦則是該主題的訂閱者。
值得注意的是,MQTT 客戶端在通信時,角色往往不是單一的,一個客戶端既可以作為信息發布者也可以同時作為信息訂閱者。如下圖所示:
上圖中的所有客戶端都是圍繞“LED 控制”這一主題進行通信。此時,對于“LED 控制”這一主題來說,手機和電腦客戶端成為了MQTT 信息的發布者而開發板則成為了MQTT 信息的訂閱者(接收者)。
所以由此可知,針對不同的主題,MQTT 客戶端可以切換自己的角色,它們可能對主題A 來說是信息發布者,但是對于主題B 就成了信息訂閱者。
MQTT 發布/訂閱特性
MQTT 發布/訂閱的特性:客戶端相互獨立、空間上可分離、時間上可異步,具體介紹如下:
? 客戶端相互獨立:MQTT 客戶端是一個個獨立的個體,它們無需了解彼此的存在,依然可以實現信息交流。譬如在上面的實例中,開發板客戶端在發布“芯片溫度”信息時,開發板客戶端本身完全不知道有多少個MQTT 客戶端訂閱了“芯片溫度”這一主題;而訂閱了“芯片溫度”主題的手機和電腦客戶端也完全不知道彼此的存在,大家只要訂閱了“芯片溫度”這一主題,MQTT 服務端就會在每次收到新信息時,將信息發送給訂閱了“芯片溫度”主題的客戶端。
? 空間上分離:空間上分離相對容易理解,MQTT 客戶端以及MQTT 服務端它們在通信時是處于同一個通信網絡中的,這個網絡可以是互聯網或者局域網;只要客戶端聯網,無論他們遠在天邊還是近在眼前,都可以實現彼此間的通訊交流;其實網絡通信本就是如此,所以并不是MQTT 通信所特有的。
? 時間上可異步:MQTT 客戶端在發送和接收信息時無需同步。這一特點對物聯網設備尤為重要,前面我們也介紹了,MQTT 從誕生之初就是專為低帶寬、高延遲或不可靠的網絡而設計的,高延遲和不可靠網絡必然就會導致時間上的異步;物聯網設備在運行過程中發生意外掉線是非常正常的情況,我們使用上面的實例二的場景來作說明,當開發板在運行過程中,可能會由于突然斷電(假設開發板是通過電源適配器供電的)導致掉線,這時開發板會斷開與MQTT 服務端的連接。假設此時我們的手機客戶端向開發板客戶端所訂閱的“LED 控制”主題發布了信息,而開發板恰恰不在線,這時,MQTT 服務端可以將“LED 控制”主題的新信息保存,待開發板客戶端再次上線后,服務端再將“LED 控制”信息推送給開發板。所以這就必然導致了,手機發送信息與開發板接收信息在時間上是異步的。
連接MQTT 服務端
MQTT 客戶端之間想要實現通信,必須要通過MQTT 服務端,都必須先連接到服務端。
MQTT 客戶端連接服務端總共包含了兩個步驟:
①、首先客戶端需要向服務端發送連接請求,這個連接請求實際上就是向服務端發送一個CONNECT報文,也就是發送了一個CONNECT 數據包。
②、MQTT 服務端收到連接請求后,會向客戶端發送連接確認。連接確認實際上是向客戶端發送一個CONNACK 報文,也就是CONNACK 數據包。
CONNECT 請求報文
CONNECT 報文包含的信息如下圖所示:
所謂報文就是一個數據包,MQTT 報文組成分為三個部分:固定頭(Fixed header)、可變頭(Variableheader)以及有效載荷(Payload,消息體)。這里我們簡單地介紹一下:
?固定頭(Fixed header):存在于所有MQTT 報文中,固定頭中有報文類型標識,可用于識別是哪種MQTT 報文,譬如該報文是CONNECT 報文還是CONNACK 報文,亦或是其它類型報文。
? 可變頭(Variable header):存在于部分類型的MQTT 報文中,報文的類型決定了可變頭是否存在及其具體的內容。
? 消息體(Payload):存在于部分類型的MQTT 報文中,payload 就是消息載體的意思。
CONNECT 報文包含了很多的信息,左邊的是信息的名稱(變量名),右邊則是信息的具體內容(變量的值),右邊這些具體內容只是一個示例。
有些信息名稱旁邊標注了“可選”字樣,而有些則沒有。
沒有標注“可選”字樣的信息是必須包含在CONNECT 報文中的。
先介紹一下未標注“可選”字樣的信息。
clientId–客戶端id
clientId 是MQTT 客戶端的標識,也就是MQTT 客戶端的名字,MQTT 服務端可通過clientId 來區分不同的客戶端,MQTT 服務端用該標識來識別客戶端。因此clientId 必須是獨立的,如果兩個MQTT 客戶端使用相同clientId 標識,服務端會把它們當成同一個客戶端來處理。通常clientId 是由一串字符所構成的,譬如,在上面的示例中,clientId 是“client-id”。
keepAlive–心跳時間間隔
對于MQTT 服務器來說,它要判斷一臺MQTT 客戶端是否依然與它保持著連接狀態,可以檢查這臺客戶端是不是經常發送消息給服務端,如果服務端經常收到客戶端的消息,那么沒問題,這個客戶端肯定在線。
但是有些客戶端并不經常發送消息給服務端,對于這種客戶端,MQTT 協議使用了類似心跳檢測的方法來判斷客戶端是否在線。前面在介紹MQTT 時,曾提到過MQTT 支持心跳機制,心跳機制其實就是用來判斷客戶端是否與服務端保持著連接的一種方法,也就是說通過心跳機制來檢測客戶端是否在線。客戶端在沒有向服務端發送信息時(空閑時),可以定時向服務端發送一個心跳數據包,這個心跳包也被稱作心跳請求,心跳請求的作用正是用于告知服務端,當前客戶端依然在線,服務端在收到客戶端的心跳請求后,會回復一條消息,這條回復消息被稱作心跳響應。
譬如keepAlive=60,表示告訴服務端,客戶端將會每隔60 秒左右向服務端發送心跳包。
cleanSession–清除會話
這是一個布爾值,cleanSession 標志可用于控制客戶端與服務端在連接和斷開連接時的行為。在離線期間你的QQ 好友給你發了幾條信息;由于當前你的QQ 處于離線狀態,自然是接收不到好友發送過來的信息,但是,當你的QQ 恢復連接狀態時,立馬會接收到好友在離線期間所發給你的信息。
而cleanSession 就與這個有關系,它是一個布爾值,如果連接服務端時cleanSession=0,當MQTT 客戶端由離線(與服務端斷開連接)再次上線時,離線期間發給客戶端的所有QoS>0 的消息仍然可以接收到;如果連接服務端時cleanSession=1,當MQTT 客戶端由離線(與服務端斷開連接)再次上線時,離線期間發給客戶端的所有消息一律接收不到。
下面再看看MQTT 服務端接收到客戶端發來的連接請求后所回復的CONNACK 報文詳細內容。
CONNACK 回復報文
CONNACK 報文包含的信息如下圖所示:
returnCode–連接返回碼
當服務端收到了客戶端的連接請求后,會向客戶端發送returnCode(連接返回碼),用來說明連接情況。見下表:
| 0 | 連接成功 |
| 1 | 連接被服務端拒絕,原因是不支持客戶端的MQTT 協議版本 |
| 2 | 連接被服務端拒絕,原因是不支持客戶端標識符的編碼。可能造成此原因的是客戶端標識符編碼是UTF-8,但是服務端不允許使用此編碼。 |
| 3 | 連接被服務端拒絕,原因是服務端不可用。即,網絡連接已經建立,但MQTT服務不可用。 |
| 4 | 連接被服務端拒絕,原因是用戶名或密碼無效。 |
| 5 | 連接被服務端拒絕,原因是客戶端未被授權連接到此服務端。 |
| 6~255 | 保留備用 |
sessionPresent
sessionPresent 與CONNECT 報文中的cleanSession 有關系。
在cleanSession=0 的情況下,當客戶端連接到服務器之后,可通過CONNACK 報文中返回的sessionPresent 來查詢服務端是否為客戶端保存了會話狀態(客戶端上一次連接時的會話狀態信息),如果服務端已為客戶端保存了上一次連接時的會話狀態,則sessionPresent=1,如果沒有保存會話狀態,則sessionPresent=0。
如果cleanSession=1,在這種情況下,客戶端是不需要服務端保存會話狀態的,那么服務端發送的確認連接CONNACK 報文中,sessionPresent 肯定是false(sessionPresent=0)。
Tips:關于MQTT 協議的參考資料,鏈接地址如下:
http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718027
斷開連接
當MQTT 客戶端連接到服務端之后,客戶端可以主動向服務端發送一個DISCONNECT 報文來斷開與服務端的連接,如下圖所示:
發布消息、訂閱主題與取消訂閱主題
PUBLISH–發布消息報文
當客戶端連接到服務端之后,就可以向服務端發布消息了,每條發布的消息必須指定一個“主題”,表示向某主題發布消息;MQTT 服務端可以通過主題來確定將消息轉發給哪些客戶端(訂閱了該主題的客戶端)。
MQTT 客戶端向服務端發布消息其實就是向服務端發送一個PUBLISH 報文,服務端收到客戶端發送過來的PUBLISH 報文之后,會向發送發回復一個報文。
下圖是PUBLISH 報文包含的信息:
packetId–報文標識符
報文標識符可用于對MQTT 報文進行標識(識別不同的報文)。不同的MQTT 報文所擁有的標識符不同。MQTT 設備可以通過該標識符對MQTT 報文進行甄別和管理,MQTT 協議內部使用的標識符。請注意:報文標識符的內容與QoS 級別有密不可分的關系。只有QoS 級別大于0 時,報文標識符才是非零數值。如果QoS 等于0,報文標識符為0,為什么是這樣的呢?后面向大家介紹QoS 概念時會做一個簡單地說明!
topicName–主題名字
這個就是發布消息時對應的主題的名字,這是一個字符串,譬如上圖中
topicName=“myTopic”,表示會將消息發布到“myTopic”這個主題。
payload–有效載荷
有效載荷是我們希望通過MQTT 所發送的實際內容。我們可以使用MQTT 協議發送字符串文本,圖像等格式的內容。這些內容都是通過有效載荷所發送的。
qos–服務質量等級
QoS(Quality of Service)表示MQTT 消息的服務質量等級。QoS 有三個級別:0、1 和2,QoS 決定MQTT 通信有什么樣的服務保證。有關QoS 的詳細信息我們會在后續內容中詳細講解。
retain–保留標志
在默認情況下,當客戶端訂閱了某一主題后,并不會馬上接收到該主題的信息。因為客戶端訂閱該主題之后,并沒有其它客戶端向該主題發布消息;只有在客戶端訂閱該主題后,服務端接收到該主題的新消息時,服務端才會將最新接收到的該主題消息推送給客戶端。
但是在有些情況下,我們需要客戶端在訂閱了某一主題后馬上接收到一條該主題的信息。這時候就需要用到保留標志這一信息。關于保留標志的具體使用方法,我們將在本教程的后續部分進行詳細講解。
dup–重發標志
dup 標志指示此消息是否重復。
當MQTT 報文的接收方沒有及時向報文發送發回復確認收到報文時,發送方會以為對方沒有收到信息,會再次重復發送MQTT 報文(譬如客戶端向服務端發送PUBLISH 報文,服務端收到PUBLISH 報文之后需要向客戶端回復一個PUBACK 報文,如果客戶端沒收到PUBACK 報文,則會認為服務端可能沒接收到自己發送的報文,將會再次發送PUBLISH 報文)。在重復發送MQTT 報文時,發送方會將此“dup–重發標志”設置為true。請注意,重發標志只在QoS 級別大于0 時使用。有關QoS 的詳細信息,我們將會在后續內容中為您做詳細介紹。
SUBSCRIBE–訂閱主題報文
客戶端要想訂閱主題,首先要向服務端發送主題訂閱請求。客戶端是通過向服務端發送SUBSCRIBE 報文來實現這一請求的。該報文包含有一系列“訂閱主題名”。一個SUBSCRIBE 報文可以包含有單個或者多個訂閱主題名。
另外每一個SUBSCRIBE 報文還包含有“報文標識符”。報文標識符可用于對MQTT 報文進行標識。不同的MQTT 報文所擁有的標識符不同。MQTT 設備可以通過該標識符對MQTT 報文進行甄別和管理。
當客戶端向服務端發送SUBSCRIBE 報文,服務端接收到SUBSCRIBE 報文之后會向客戶端回復一個SUBACK 報文(訂閱確認報文),如下圖所示:
服務端接收到客戶端的訂閱報文后,會向客戶端發送SUBACK 報文確認訂閱。
SUBACK 報文包含有“訂閱返回碼”和“報文標識符”這兩個信息。
returnCode–訂閱返回碼
客戶端向服務端發送訂閱請求后,服務端會給客戶端返回一個訂閱返回碼。
客戶端可通過一個SUBSCRIBE 報文發送多個主題的訂閱請求。服務端會針對SUBSCRIBE 報文中的所有訂閱主題來逐一回復給客戶端一個返回碼。
這個返回碼的作用是告知客戶端是否成功訂閱了主題。以下是返回碼的詳細說明。
| 0x00 | 訂閱成功–QoS0 |
| 0x01 | 訂閱成功–QoS1 |
| 0x02 | 訂閱成功–QoS2 |
| 0x80 | 訂閱失敗 |
由上表可知,當returnCode=0、1 或2 這三種情況時,都表示訂閱成功;具體返回的數字是多少,根據訂閱主題時QoS 的不同,服務端的返回碼也會有所不同。
UNSUBSCRIBE–取消訂閱主題報文
客戶端可通過向服務端發送一個UNSUBSCRIBE 報文來取消訂閱主題,當服務端接收到UNSUBSCRIBE報文后,會向發送發回復一個UNSUBACK 報文(取消訂閱確認報文),如下圖所示:
UNSUBSCRIBE 報文包含兩個重要信息,第一個是取消訂閱的主題名稱,同一個UNSUBSCRIBE 報文可以同時包含多個取消訂閱的主題名稱。另外,UNSUBSCRIBE 報文也包含“報文標識符”,MQTT 設備可以通過該標識符對報文進行管理。
當服務端接收到UNSUBSCRIBE 報文后,會向客戶端發送取消訂閱確認報文–UNSUBACK 報文。該報文含有客戶端所發送的“取消訂閱報文標識符”。
客戶端接收到UNSUBACK 報文后就可以確認取消主題訂閱已經成功完成了。
主題的格式
1、主題的基本形式
主題的基本形式就是一個字符串,譬如:“myTopic”、“currentTemp”、“LEDControl"等,雖然看起來簡單,但是有幾個點需要大家注意一下:
? 主題是區分大小寫的。所以"LEDControl"和"ledControl"是兩個不同的主題。
? 主題可以使用空格。譬如"LED Control”,雖然主題允許使用空格,但是筆者建議大家盡量不要使用空格。
? 不要使用中文主題。雖然有些MQTT 服務器支持中文主題,但是絕大部分MQTT 服務器是不支持中文主題的,所以大家不要使用中文主題,而是使用ASCII 字符來作為MQTT 主題。
? 以 $ 號開頭的主題是MQTT 服務端系統保留的特殊主題,客戶端不可隨意訂閱或向其發布信息,譬如:
? 盡量不要使用“/”作為主題的開頭,這樣做沒有什么意義,而且額外產生一個沒有用處的主題級別。
?主題中盡量使用ASCII 字符
雖然有些MQTT 設備支持UTF-8 字符作為MQTT 主題,但是筆者建議您在主題中盡量使用ASCII 字符。
2、主題分級
MQTT 主題可以是一個簡單的字符串,譬如:“myTopic”、“currentTemp”、“LEDControl”,事實上,MQTT協議為了更好的對主題進行管理和分類,支持主題分級,對主題進行分級處理,各個級別之間使用" / "符號進行分隔。如下所示:
在以上示例中一共有四級主題,分別是第1 級home、第2 級sensor、第三級led、第4 級brightness。主題的每一級至少需要一個字符;而只有一個簡單字符串的主題,譬如"myTopic"、“currentTemp”、“LEDControl”,這些都是單一級別的主題。
我們再來看幾個分級主題的示例:
"home/sensor/kitchen/temperature" "home/sensor/kitchen/brightness" "home/sensor/bedroom/temperature" "home/sensor/bedroom/brightness"需要注意的是,主題名稱不要使用" / "開頭,譬如:
"/home/sensor/led/brightness"這樣是不行的。
3、主題通配符
當客戶端訂閱主題時,可以使用通配符同時訂閱多個主題。通配符只能在訂閱主題時使用,下面我們將介紹兩種通配符:單級通配符和多級通配符。
單級通配符:+
單級通配符可以匹配任意一個主題級別,注意是一個主題級別,譬如示例如下:
"home/sensor/+/status"當客戶端訂閱了上述主題之后,將會收到以下主題的信息內容:
"home/sensor/led/status" "home/sensor/key/status" "home/sensor/beeper/status" ......相反,而以下這些主題的信息是無法接收到的:
"dt/sensor/led/status" "home/kash/key/status" "home/sensor/led/brightness" ......以上這些注意將無法接收到,原因在于這些主題無法與"home/sensor/+/status"相匹配。
這就是單級通配符的概念。
多級通配符:#
多級通配符自然是可以匹配任意數量個主題級別,而不再是單一主題級別,多級通配符使用“#”號來表示,譬如:
當客戶端訂閱了上面這個主題之后,便可以收到如下注意的信息:
"home/sensor/led" "home/sensor/key" "home/sensor/beeper" "home/sensor/led/status" "home/sensor/led/brightness" "home/sensor/key/status" "home/sensor/beeper/status" ......相反,如下主題的信息是無法接收到的:
"home/kash/led" "dt/sensor/led" "dt/kash/led" ......這就是多級通配符的概念。
MQTT 初體驗
初次體驗,我們將使用電腦作為客戶端,而服務端我們將使用公用MQTT 服務器。
下載、安裝MQTT.fx 客戶端軟件
想要將電腦作為MQTT 客戶端,我們需要在電腦上安裝一個MQTT 客戶端軟件,推薦MQTT.fx 這款軟件,官網是http://mqttfx.jensd.de/。
以1.7.1 版本為例,進入到http://www.jensd.de/apps/mqttfx/1.7.1/鏈接地址進行下載,如下:
根據自己的需求下載即可!筆者使用的是Windows 64 位系統,所以筆者下載的是最后一個,下載完成之后得到一個exe 安裝包文件:
直接雙擊即可安裝,打開軟件之后界面如下所示:
MQTT 服務端
自己搭建MQTT 服務器
本章我們不使用自己搭建的服務器,因為如果你沒有公網IP 的主機,搭建的服務器也只能在局域網內使用,外部網絡無法接入。
作為學習,筆者還是給大家簡單地提一下關于自己如何去搭建MQTT 的服務器。MQTT 服務器非常多,如apache 的ActiveMQ、emtqqd、HiveMQ、Emitter、Mosquitto、Moquette 等等,既有開源的服務器也有商業服務器;作為學習,我們推薦Mosquitto,Mosquitto 是一個高質量輕量級的開源MQTT Broker,支持MQTTv3.1 和MQTTv3.1.1 協議,也是目前主流的開源MQTT Broker,它的官方地址是https://mosquitto.org/。
公用MQTT 服務器
除了自己搭建服務器之外,我們還可以使用現有的MQTT 服務器,譬如阿里云、百度云、華為云等提供的MQTT 服務,不過這些都是收費的;可以使用公用MQTT 服務器,免費供大家學習測試使用。
test.mosquitto.org(國外) MQTT 服務器地址:test.mosquitto.org TCP 端口:1883 TCP/TLS 端口:8883 WebSockets 端口:8080 Websocket/TLS 端口:8081broker.hivemq.com(國外) MQTT 服務器地址:broker.hivemq.com TCP 端口:1883 WebSockets 端口:8000iot.eclipse.org(國外) MQTT 服務器地址:broker.hivemq.com TCP 端口:1883 WebSockets 端口:8000以上幾個公用MQTT 服務器都是國外的,大家可能因為網絡的問題連接不上或者連接很慢,延遲會比較大;以下幾個則是國內的公用MQTT 服務器:
然也物聯(國內) 官網地址:http://www.ranye-iot.net MQTT 服務器地址:test.ranye-iot.net TCP 端口:1883 TCP/TLS 端口:8883通信貓(國內) MQTT 服務器地址:mq.tongxinmao.com TCP 端口:1883動手測試
現在我們要動手進行MQTT 通信測試了,首先打開之前安裝的MQTT 客戶端軟件MQTT.fx:
打開之后如上圖所示,點擊上圖中紅框標注的齒輪按鈕打開配置界面:
首先點擊1 所示的“+”號創建一個新的配置,然后填寫好各個配置參數,筆者使用的然也物聯提供的公用MQTT 服務器,服務器地址test.ranye-iot.net,對應的端口號為1883;clientId、keepAlive、cleanSession(勾選Clean Session 表示cleanSession=1)。
配置完成之后,點擊3 所示的Apply 按鈕應用配置,最后點擊右上角“X”關閉窗口:
連接成功之后如下所示:
訂閱主題
我們先訂閱一個主題,如下圖所示:
首先點擊1 所示的“Subscribe”切換到主題訂閱界面,接著填寫需要訂閱的主題名稱,示例中我們使用了分級主題“dt2914/testTopic”,最后點擊3 所示的“Subscribe”按鈕向服務器發出訂閱請求。訂閱成功之后如下圖所示:
左邊會列舉出當前客戶端所訂閱的主題,右邊顯示消息列表。
發布消息
上例中我們的電腦客戶端已經成功訂閱了主題“dt2914/testTopic”,接下來我們嘗試向該主題發布消息。如下圖所示:
首先點擊1 所示的“Publish”字樣切換到發布消息界面,在2 所示處填寫主題名稱,這里筆者需要向“dt2914/testTopic”主題發布消息;在3 所示空白處填寫需要發布的內容,然后點擊4 所示的“Publish”按鈕發布消息。
此時我們的電腦客戶端便會接收到自己發布的消息,如下所示:
取消訂閱主題
取消訂閱主題非常簡單,如下圖所示:
使用手機作為客戶端
手機也可以作為MQTT 客戶端,同樣也需要安裝一個客戶端軟件MQTTool 工具。
打開該軟件然后進行配置、連接MQTT 服務器,如下所示:
連接成功之后如下所示:
然后點擊下邊的“Subscribe”按鈕可訂閱主題、點擊“Publish”按鈕可發布消息。
現在我們進行一個測試,在前面的示例中,我們的電腦客戶端訂閱了“dt2914/testTopic”主題,現在我們要使用手機客戶端向“dt2914/testTopic”主題發布消息,如下所示:
點擊“Publish”按鈕發布消息,接著電腦客戶端便會接收到手機發布消息,如下所示:
然也物聯提供的公用MQTT 服務器,在測試過程有幾個問題需要注意下:
MQTT 協議(下)
QoS 等級
QoS 是Quality of Service 的縮寫,所以中文名便是服務質量。MQTT 協議有三種服務質量等級:
? QoS = 0:最多發一次;
? QoS = 1:最少發一次;
? QoS = 2:保證收一次。
QoS = 0:最多發一次
0 是服務質量QoS 的最低級別。當QoS 為0 級時,MQTT 協議并不保證所有信息都能得以傳輸。
也就是說發送一次之后就不管了,最多一次,不管發送是否失敗!發送端一旦發送完消息后,就完成任務了,發送端不會檢查發出的消息能否被正確接收到。
QoS = 1:最少發一次
當QoS 級別為1 時,發送端在消息發送完成后,會檢查接收端是否已經成功接收到了消息,如下圖所示:
發送端向接收端發送PUBLISH 報文,當接收端收到PUBLISH 報文后會向發送端回復一個PUBACK 報文,如果發送端收到PUBACK 報文,那么它就知道消息已經被接收端成功接收!
假如過了一段時間后,發送端沒有收到PUBACK 報文,那么發送端會再次發送消息(發送PUBLISH報文),然后再次等待接收端的PUBACK 確認報文。
當發送端重復發送一條消息時,會將PUBLISH 報文中的dup 標志設置為true,如圖33.2.8 中所示。這就是為了告訴接收端,此消息為重復發送的消息,那么我們的MQTT 客戶端在接收到消息之后,可以去判斷dup 標志以確定此消息是否為重復消息,應用程序應該對此作出相應的處理。
注意:Qos=1 時,MQTT 服務器是不會進行去重的。
QoS = 2:保證收一次
MQTT 服務質量最高級是2 級,即QoS=2。當MQTT 服務質量為2 級時,MQTT 協議可以確保接收端只接收一次消息(注意是只接收到一次,在QoS=1 的情況下,接收端接收到消息的次數可能不止一次:>=1)。
為了確保接收端只接收到一次消息,PUBLISH 報文的收發過程相對更加復雜。發送端需要接收端進行兩次消息確認,因此,2 級MQTT 服務質量是最安全的服務級別,也是最慢的服務級別。我們來看看整體的過程:
從上到下,按照1、2、3、4 的順序進行:
①、首先發送端向接收端發送PUBLISH 報文;
②、接收端接收到PUBLISH 報文后,向發送端回復一個PUBREC 報文(官方稱其為–發布收到);
③、發送端接收到PUBREC 報文后,會再次向接收端發送PUBREL 報文(官方稱其為–發布釋放);
④、接收端接收到PUBREL 報文后,會再次向發送端回復一個PUBCOMP 報文(官方稱其為–發布完成),如果發送端接收到PUBCOMP 報文表示消息傳輸成功,它確認接收端已經成功接收到消息,整個過程結束。
如何設置QoS?
點擊選中QoS0 時表示發布消息時將QoS 等級設置為最低級0。
點擊選中QoS1 時表示發布消息時將QoS 等級設置為1。
點擊選中QoS2 時表示發布消息時將QoS 等級設置為最高級2。
點擊選中QoS0 時表示訂閱主題時將QoS 等級設置為最低級0。
點擊選中QoS1 時表示訂閱主題時將QoS 等級設置為1。
點擊選中QoS2 時表示訂閱主題時將QoS 等級設置為最高級2。
要想實現QoS>0 的MQTT 通信,客戶端在連接服務端時務必要將cleanSession 設置為false。如果cleanSession 設置為true,則意味著客戶端不會接收到任何離線消息。
服務質量降級
假如客戶端在發布消息和訂閱主題時使用不同級別的QoS,如何來應對這一情況呢?
在這種情況下,服務端會使用較低級別QoS 來提供服務。如上圖所示,雖然A 發送到主題1 的消息采用QoS 為2,但是服務端發送主題1 的消息給B 時,采用的QoS 為1。這是因為B 在訂閱主題1 時采用的QoS 為1。
總之,對于發布和訂閱消息的客戶端,服務端會主動采用較低級別的QoS 來實現消息傳輸。
保留消息
前面我們提到,PUBLISH 報文中有一個retain 標志,也就是保留標志,是一個布爾值。
作用就是讓服務端對客戶端發布的消息進行保留,如果有其它客戶端訂閱了該消息對應的主題時,服務端會立即將保留消息推送給訂閱者,而不必等到發送者向主題發布新消息時訂閱者才會收到消息。
更新保留消息
每一個主題只能有一個“保留消息”,如果客戶端想要更新“保留消息”,就需要向該主題發送一條新的“保留消息”,這樣服務端會將新的“保留消息”覆蓋舊的“保留消息”。
刪除保留消息
只需向該主題發布一條空的“保留消息”即可。
使用MQTT.fx 客戶端進行測試
打開MQTT.fx 客戶端軟件,連接好服務器,先向某個主題發布消息,譬如“dt2914/testTopic”主題:
上圖中右邊的“Retained”按鈕也就是保留標志,當點擊選中時表示使能“保留消息”這一功能,如果未選中,則表示禁用“保留消息”這一功能,也就是消息不保留。我們先不保留消息,填寫好需要發送的內容之后,點擊“Publish”按鈕發布消息。
現在我們訂閱“dt2914/testTopic”主題:
訂閱之后我們的客戶端并沒有接收到前面發布的消息,因為發布在前、訂閱在后,必須要等到發布者下一次向該主題發布新消息時才會收到。
現在取消訂閱主題“dt2914/testTopic”,然后再向主題“dt2914/testTopic”發布消息,此時我們使能“保留消息”這一功能,如下:
點擊Publish 按鈕發布消息,之后我們再重新訂閱主題“dt2914/testTopic”,如下:
當訂閱之后我們的客戶端立馬就收到了一條消息,并且還標識了這條消息是“保留消息”。
MQTT 的心跳機制
心跳機制的原理在于:讓客戶端在沒有向服務端發送消息的這個空閑時間里,定時向服務端發送一個心跳包,這個心跳包被稱為心跳請求,其實質就是向服務端發送一個PINGREQ 報文;當服務端收到PINGREQ 報文后就知道該客戶端依然在線,然后向客戶端回復一個PINGRESP 報文,稱為心跳響應,如下圖所示:
由于心跳請求是定時發送的(通過keepAlive 設置時間間隔,也是告訴服務端,客戶端將會多少多少秒向它發送心跳請求,這樣服務端就會知道了);一旦服務器未收到客戶端的心跳包,那么服務器就會知道,這臺客戶端可能已經掉線了。
這個心跳機制不僅可以用于服務端判斷客戶端是否在線,客戶端也可使用心跳機制來判斷自己與服務端是否保持連接。如果客戶端在發送心跳請求(PINGREQ)后,沒有收到服務端的心跳響應(PINGRESP),那么客戶端就會認為自己與服務端已經斷開連接了。
MQTT 的遺囑機制
客戶端斷開與服務端的連接通常是有兩種方式的:
? 客戶端主動向服務端發送DISCONNECT 報文,請求斷開連接,自然服務端也就知道了客戶端要離線了;
? 客戶端意外掉線。被動與服務端斷開了連接。
MQTT 協議允許客戶端在“活著”的時候就寫好遺囑,這樣一旦客戶端意外斷線,服務端就可以將客戶端的遺囑公之于眾。
客戶端如何設置自己的“遺囑”信息
客戶端連接服務端時發送的CONNECT 報文中有這樣幾個參數,如下圖紅框中所示:
這幾個參數都是以will 開頭的,will 其實就是“遺囑”的英文單詞,下面分別介紹一下:
willTopic – 遺囑主題
遺囑消息和普通MQTT 消息很相似,也有主題和正文內容。willTopic 的作用正是告知服務端,本客戶端的遺囑主題是什么。只有那些訂閱了這一遺囑主題的客戶端才會收到本客戶端的遺囑消息。
willMessage – 遺囑消息
遺囑消息定義了遺囑的內容。在本示例中,那些訂閱了主題“clientWill”的客戶端會在客戶端意外斷線時,收到服務端發布的“client offline”這樣的信息。
willRetain – 遺囑消息的保留標志
遺囑消息也可以設置為保留標志,用于告訴服務端是否需要對遺囑消息進行保留處理。
willQoS – 遺囑消息的QoS
對于遺囑消息來說,同樣可以使用服務質量來控制遺囑消息的傳遞和接收。這里的服務質量與普通
MQTT 消息的服務質量是一樣的概念。也可以設置為0、1、2。對于不同的服務質量級別,服務端會使用不同的服務質量來發布遺囑消息。
MQTT.fx 如何設置客戶端的“遺囑”
MQTT.fx 軟件如何為電腦客戶端設置遺囑呢?首先進入到配置頁面中,如下所示:
MQTT 用戶密碼認證
username(用戶名)和password(密碼),這里的用戶名和密碼是客戶端連接服務端時進行認證所需要的。
有些MQTT 服務端需要客戶端在連接時提供用戶名和密碼,只有客戶端正確提供了用戶名和密碼后,才能連接服務端,否則服務端將會拒絕客戶端連接,那么客戶端也就無法發布和訂閱消息了。
接下來我們使用MQTT.fx 來測試一下,筆者使用的是然也物聯提供的公共版MQTT 服務器,這個服務器提供了一個測試用的用戶名和密碼,用戶名是test-user、密碼是ranye-iot,那我們使用這個用戶名和密碼連接服務端試試:
填寫完用戶名、密碼之后點擊右下角“Apply”按鈕應用,然后關閉配置窗口。然后點擊Connect 按鈕連接服務端:
點擊Connect 按鈕連接服務器,就會成功連接上。在手機上使用MQTTool 工具也可以,大家自己去試試。
用戶名和密碼除了用于在連接服務端時進行認證、校驗這一功能外,有些MQTT 服務端也利用此信息來識別客戶端屬于哪一個用戶,從而對客戶端進行管理。
申請社區版MQTT 服務
在前面的示例中,我們都是使用的公共版MQTT 服務器進行了測試,只要輸入了正確的服務器地址就可以連接,大家都可以對相同的主題發布消息、訂閱該主題,導致我們發布的信息誰都能看到。
然也物聯平臺也提供了免費的社區版MQTT 服務,社區版MQTT 服務是面向個人用戶的免費MQTT 服務。與公共版MQTT 服務不同的是,社區版MQTT 服務中,用戶個人主題和信息傳輸受到用戶名和密碼保護。即,A 用戶的個人主題只有A 用戶可以發布和訂閱,其他用戶不能對該主題進行訂閱和發布,這樣會使得安全性得到提升。
當客戶端連接社區版MQTT 服務端時,需要提供正確的用戶名和密碼,服務端會對此進行驗證,如果沒有提供正確的用戶名、密碼信息,則服務器將拒絕為用戶提供MQTT 服務,也就是拒絕客戶端連接。所以,我們個人用戶可使用然也物聯的社區版MQTT 服務來搭建自己的私人物聯網項目,注意僅限于個人用戶使用,不可商用!
那接下來,筆者將向大家介紹如何通過然也物聯平臺申請社區版MQTT 服務。
首先進入到然也物聯的官方網站:http://www.ranye-iot.net/
點擊上邊的“注冊用戶”注冊一個用戶:
大家根據指示填寫信息,完成用戶注冊。
注冊完成之后登陸然也物聯平臺,登陸成功之后如下所示:
左上角會出現一個“平臺申請”鏈接,點擊“平臺申請”鏈接即可申請然也物聯的社區版MQTT 服務,如下所示:
同樣,大家根據指示說明填寫好信息。在填寫信息之前,仔細閱讀相關說明以及相應的要求,最后有三個題目需要大家填寫正確,只有正確回答所有問題最終才會顯示“提交申請”這個按鈕。這樣做為了防止申請服務的用戶是真正需要使用MQTT 服務的用戶、而不是隨隨便便的一個用戶。
當我們提交申請之后,頁面會出現一個提示信息,這個大家要認真看一下,提示中說到:然也物聯官方工作人員會在未來幾天之內添加你申請時留下的微信號,以人工的形式進一步完成申請審核。所以大家要留意下自己的微信,未來幾天內會不會有然也物聯官方工作人員添加你為好友,到時你要同意一下。
當我們的申請通過之后,官方工作人員會通過微信通知我們,告訴我們申請已經成功了!接著工作人員會將相關的使用注意事項、使用方法通過微信發送給您,大家要認真閱讀尤其是注意事項;如果違反了它的規定,將會停止您使用社區版服務。
除了注意事項之外,還包括對于我們使用社區版MQTT 服務非常重要的信息,譬如社區版MQTT 服務器的地址(iot.ranye-iot.net)、端口(1883)以及然也物聯給我們提供的客戶端連接認證信息。
然也物聯給每一個申請的用戶提供了8 組客戶端連接認證信息,也就是8 組用戶名、密碼、clientId,也就是說允許我們同時使用8 臺客戶端設備連接服務端;每一臺客戶端設備連接服務端時使用其中一組用戶名、密碼、clientId 信息,只有用戶名、密碼、clientId 匹配、服務端的認證才會通過、才可成功連接到服務端。
社區版MQTT 服務器為用戶提供了個人專屬主題級別,只有用戶自己的客戶端設備才可以使用自己的個人專屬主題級別,譬如向個人專屬主題發布消息、訂閱個人專屬主題;而其它用戶是無法向您的個人專屬主題發布消息、也不能訂閱您的個人專屬主題,因為社區版MQTT 服務中個人專屬主題級別受到了用戶名、密碼保護,同樣您也不能向其他用戶的個人專屬主題發布消息以及訂閱其他用戶的個人專屬主題;這樣使得我們的MQTT 物聯網通信安全性大大提升!
在后續我們會自己編寫一個MQTT 客戶端程序,在我們的開發板上運行,將開發板作為MQTT 客戶端去連接然也物聯社區版MQTT 服務器。
移植MQTT 客戶端庫
前面的示例中,我們使用MQTT.fx 客戶端軟件在自己的電腦上進行了測試,如果需要在開發板上進行測試,將開發板作為MQTT 客戶端,我們需要自己去編寫客戶端程序。
首先在編寫客戶端程序之前,需要移植MQTT 客戶端庫到我們的開發板上。
下載MQTT 客戶端庫源碼
如何下載MQTT 客戶端庫源碼包?首先我們進入到MQTT 的官網地址:https://mqtt.org/
點擊“Software”鏈接地址,找到“Client libraries”項,如下所示:
MQTT 客戶端庫支持多種不同的編程語言,譬如C、C++、Go、Java、Lua、Objective-C、Python 等,對于我們來說,我們使用的是C 語言開發,所以要選擇MQTT C 客戶端庫,如下所示:
這里有多種不同的MQTT C 客戶端庫,筆者推薦大家使用第一個Eclipse Paho C,這是一個“MQTT C Client for Posix and Windows”,Paho MQTT C 客戶端庫是用ANSI 標準C 編寫的功能齊全的MQTT 客戶端庫,可運行在Linux 系統下,支持MQTT3.1、MQTT3.1.1、MQTT5.0。
點擊“Eclipse Paho C”鏈接地址,如下:
在這個頁面中會有一些簡單地介紹信息,大家可以自己看一看。我們往下看,找到它的下載地址:
點擊右邊的“Release”找到它的發布版本,如下所示:
目前最新的版本是1.3.9,我們不使用最新版本,建議大家使用1.3.8 版本,如上圖所示,點擊“Source code (tar.gz)”鏈接地址下載客戶端庫源碼。
下載成功之后會得到如下壓縮文件:
交叉編譯MQTT C 客戶端庫源碼
將paho.mqtt.c-1.3.8.tar.gz 壓縮文件拷貝到Ubuntu 系統某個目錄下,如下所示:
接著將其解壓到當前目錄,如下所示:
解壓成功之后會得到paho.mqtt.c-1.3.8 文件夾,這就是paho MQTT C 客戶端庫源碼工程,進入到該目錄下,可以看到工程頂級目錄下有一個CMakeLists.txt 文件,所以可知這是一個由cmake 構建的工程。
首先我們要新建一個交叉編譯配置文件arm-linux-setup.cmake,進入到cmake 目錄下,新建arm-linux-setup.cmake 文件,并輸入以下內容:
這是配置交叉編譯,需要根據自己實際情況修改。
編寫完成之后保存退出。
回到工程的頂層目錄,新建一個名為build 的目錄,如下所示:
進入到build 目錄下,執行cmake 進行構建:
~/tools/cmake-3.16.0-Linux-x86_64/bin/cmake 這是第三十二章時筆者下載的3.16.0 版本的cmake 工具,您得根據自己的實際路徑來指定;CMAKE_BUILD_TYPE 、CMAKE_INSTALL_PREFIX 、
CMAKE_TOOLCHAIN_FILE 都是cmake 變量,CMAKE_INSTALL_PREFIX 這個指定了安裝路徑,筆者將安裝路徑設置為頂層目錄下的install 目錄。
除此之外,還定義了兩個緩存變量PAHO_WITH_SSL 和PAHO_BUILD_SAMPLES,具體是什么意思大家可以自己查看工程頂級目錄下的README.md 文件,在README.md 文件中對工程的編譯進行了簡單介紹。
cmake 執行完畢之后,接著執行make 編譯:
這里需要給大家簡單地說明一下,事實上,MQTT 客戶端庫依賴于openssl 庫,所以通常在移植MQTT客戶端庫的時候,需要先移植openssl、交叉編譯openssl 得到庫文件以及頭文件,然后再來編譯MQTT 客戶端庫;但我們這里沒有去移植openssl,原因是,我們的開發板出廠系統中已經移植好了openssl 庫,并且我們所使用的交叉編譯器在編譯工程源碼的時候會鏈接openssl 庫(sysroot 路徑指定的)。
編譯成功之后,執行make install 進行安裝:
對安裝目錄下的文件夾進行簡單介紹
進入到安裝目錄下:
在安裝目錄下有bin、include、lib 以及share 這4 個文件夾,bin 目錄下包含了一些簡單的測試demo,lib 目錄下包含了我們編譯出來的庫文件,如下所示:
一共有4 種類型的庫,這里我們簡單地介紹一下:
? libpaho-mqtt3a.so:異步模式MQTT 客戶端庫(不支持SSL)。
? libpaho-mqtt3as.so:異步模式MQTT 客戶端庫(支持SSL)。
? libpaho-mqtt3c.so:同步模式MQTT 客戶端庫(不支持SSL)。
? libpaho-mqtt3cs.so:支持SSL 的同步模式客戶端庫(支持SSL)。
Paho MQTT C 客戶端庫支持同步操作模式和異步操作模式兩種,關于它們之間的區別筆者不做介紹,頂級目錄下docs/MQTTClient/html/async.html 文檔(直接雙擊打開)中對此有相應的解釋,有興趣的可以看一看;docs 目錄下提供了很多供用戶參考的文檔,包括API 使用說明、示例代碼等等,在后續的學習過程中,可以查看這些文檔獲取幫助。
MQTT 中使用SSL/TLS 來提供安全性(由openssl 提供),使用SSL 來做一些加密驗證,使得數據傳輸更加安全可靠。
以上便給大家簡單地介紹了下這4 種庫文件之間的區別,那后續我們將使用libpaho-mqtt3c.so。
介紹完庫文件之后,再來看看頭文件,進入到include 目錄下:
在我們的MQTT 客戶端應用程序中只需要包含MQTTAsync.h 或MQTTClient.h 頭文件即可,其它那些頭文件會被這兩個頭文件所包含;
MQTTAsync.h 是異步模式客戶端庫對外的頭文件,而MQTTClient.h 則是同步模式客戶端庫對外的頭文件。因為后續我們將使用同步模式,所以到時在我們的應用程序中需要包含MQTTClient.h 頭文件。
拷貝庫文件到開發板
將編譯得到的庫文件拷貝到開發板Linux 系統/usr/lib 目錄下,注意不要破壞原有的鏈接關系,建議在操作之前,先將庫文件進行打包,如下所示:
將壓縮包文件libmqtt.tar.gz 拷貝到開發板Linux 系統/home/root 目錄下,然后將其解壓到/usr/lib 目錄:
tar -xzf libmqtt.tar.gz -C /usr/libMQTT 客戶端庫API 介紹
本小節我們所介紹的這些庫函數都是定義在MQTTClient.h 頭文件中,也就是同步模式客戶端庫API,所以在我們的應用程序中需要包含頭文件MQTTClient.h。
Tips:MQTT 客戶端源碼頂級目錄docs/MQTTClient/html/index.html 文檔向用戶介紹了API 的使用方法,并且提供了相應示例代碼供用戶參考。
MQTTClient_message結構體
MQTT 客戶端應用程序發布消息和接收消息都是圍繞著這個結構體。
MQTTClient_message 數據結構描述了MQTT 消息的負載和屬性等相關信息,譬如消息的負載、負載的長度、qos、消息的保留標志、dup 標志等,但是消息主題不是這個結構體的一部分。該結構體內容如下:
typedef struct {int payloadlen; //負載長度void* payload; //負載int qos; //消息的qos 等級int retained; //消息的保留標志int dup; //dup 標志(重復標志)int msgid; //消息標識符,也就是前面說的packetId...... } MQTTClient_message;當客戶端發布消息時就需要實例化一個MQTTClient_message 對象,同理,當客戶端接收到消息時,其實也就是接收到了MQTTClient_message 對象。通常在實例化MQTTClient_message 對象時會使用MQTTClient_message_initializer 宏對其進行初始化。
創建客戶端對象
在連接服務端之前,需要創建一個客戶端對象,使用MQTTClient_create 函數創建:
int MQTTClient_create(MQTTClient *handle,const char *serverURI,const char *clientId,int persistence_type,void *persistence_context );-
handle:MQTT 客戶端句柄;
-
serverURL:MQTT 服務器地址;
-
clientId:客戶端ID;
-
persistence_type:客戶端使用的持久化類型:
? MQTTCLIENT_PERSISTENCE_NONE:使用內存持久性。如果運行客戶端的設備或系統出現故障或關閉,則任何傳輸中消息的當前狀態都會丟失,并且即使在QoS1 和QoS2 下也可能無法傳遞某些消息。
? MQTTCLIENT_PERSISTENCE_DEFAULT:使用默認的(基于文件系統)持久性機制。傳輸中消息的狀態保存在文件系統中,并在意外故障的情況下提供一些防止消息丟失的保護。
? MQTTCLIENT_PERSISTENCE_USER:使用特定于應用程序的持久性實現。使用這種類型的持久性可以控制應用程序的持久性機制。應用程序必須實現MQTTClient_persistence 接口。 -
persistence_context:如果使用MQTTCLIENT_PERSISTENCE_NONE 持久化類型,則該參數應設置為NULL。如果選擇的是MQTTCLIENT_PERSISTENCE_DEFAULT 持久化類型,則該參數應設置為持久化目錄的位置,如果設置為NULL,則持久化目錄就是客戶端應用程序的工作目錄。
-
返回值:客戶端對象創建成功返回MQTTCLIENT_SUCCESS,失敗將返回一個錯誤碼。
使用示例
MQTTClient client; int rc;/* 創建mqtt 客戶端對象*/ if (MQTTCLIENT_SUCCESS !=(rc = MQTTClient_create(&client, "tcp://iot.ranye-iot.net:1883","dt_mqtt_2_id",MQTTCLIENT_PERSISTENCE_NONE, NULL))) {printf("Failed to create client, return code %d\n", rc);return EXIT_FAILURE; }注意,"tcp://iot.ranye-iot.net:1883"地址中,第一個冒號前面的tcp 表示我們使用的是TCP 連接;后面的1883 表示MQTT 服務器對應的端口號。
連接服務端
客戶端創建之后,便可以連接服務器了,調用MQTTClient_connect 函數連接:
int MQTTClient_connect(MQTTClient handle,MQTTClient_connectOptions *options );- handle:客戶端句柄;
- options:一個指針。指向一個MQTTClient_connectOptions 結構體對象。
MQTTClient_connectOptions 結構體中包含了keepAlive、cleanSession 以及一個指向MQTTClient_willOptions 結構體對象的指針will_opts;
MQTTClient_willOptions 結構體包含了客戶端遺囑相關的信息,遺囑主題、遺囑內容、遺囑消息的QoS 等級、遺囑消息的保留標志等。
返回值:連接成功返回MQTTCLIENT_SUCCESS,是否返回錯誤碼:
? 1:連接被拒絕。不可接受的協議版本,不支持客戶端的MQTT 協議版本
? 2:連接被拒絕:標識符被拒絕
? 3:連接被拒絕:服務器不可用
? 4:連接被拒絕:用戶名或密碼錯誤
? 5:連接被拒絕:未授權
? 6-255:保留以備將來使用
使用示例:
MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer; MQTTClient_willOptions will_opts = MQTTClient_willOptions_initializer; ...... /* 連接服務器*/ will_opts.topicName = "dt2914/willTopic"; //遺囑主題 will_opts.message = "Abnormally dropped"; //遺囑內容 will_opts.retained = 1; //遺囑保留消息 will_opts.qos = 0; //遺囑QoS 等級conn_opts.will = &will_opts; conn_opts.keepAliveInterval = 30; //客戶端keepAlive 間隔時間 conn_opts.cleansession = 0; //客戶端cleanSession 標志 conn_opts.username = "dt_mqtt_2"; //用戶名 conn_opts.password = "dt291444"; //密碼 if (MQTTCLIENT_SUCCESS !=(rc = MQTTClient_connect(client, &conn_opts))) {printf("Failed to connect, return code %d\n", rc);return EXIT_FAILURE; }通常在定義MQTTClient_connectOptions 對象時會使用MQTTClient_connectOptions_initializer 宏對其進行初始化操作;而在定義MQTTClient_willOptions 對象時使用MQTTClient_willOptions_initializer 宏對其初始化。
設置回調函數
調用MQTTClient_setCallbacks 函數為應用程序設置回調函數,MQTTClient_setCallbacks 可設置多個回調函數,包括:
1、斷開連接時的回調函數cl (當客戶端檢測到自己掉線時會執行該函數,如果將其設置為NULL表示應用程序不處理斷線的情況);
2、接收消息的回調函數ma(當客戶端接收到服務端發送過來的消息時執行該函數,必須設置此函數否則客戶端無法接收消息);
3、發布消息的回調函數dc(當客戶端發布的消息已經確認發送時執行該回調函數,如果你的應用程序采用同步方式發布消息或者您不想檢查是否成功發送時,您可以將此設置為NULL)。
handle:客戶端句柄;
context:執行回調函數的時候,會將context 參數傳遞給回調函數,因為每一個回調函數都設置了一個參數用來接收context 參數。
cl:一個MQTTClient_connectionLost 類型的函數指針,如下:
typedef void MQTTClient_connectionLost(void *context, char *cause);參數cause 表示斷線的原因,是一個字符串。
ma:一個MQTTClient_messageArrived 類型的函數指針,如下:
typedef int MQTTClient_messageArrived(void *context, char *topicName,int topicLen, MQTTClient_message *message);參數topicName 表示消息的主題名,topicLen 表示主題名的長度;參數message 指向一個MQTTClient_message 對象,也就是客戶端所接收到的消息。
dc:一個MQTTClient_deliveryComplete 類型的函數指針,如下:
typedef void MQTTClient_deliveryComplete(void* context, MQTTClient_deliveryToken dt);參數dt 表示MQTT 消息的值,將其稱為傳遞令牌。發布消息時(應用程序通過MQTTClient_publishMessage 函數發布消息),MQTT 協議會返回給客戶端應用程序一個傳遞令牌;應用程序可以通過將調用MQTTClient_publishMessage()返回的傳遞令牌與傳遞給此回調的令牌進行匹配來檢查消息是否已成功發布。
前面提到了“同步發布消息”這個概念,既然有同步發布,那必然有異步發布,確實如何!那如何控制是同步發布還是異步發布呢?就是通過MQTTClient_connectOptions 對象中的reliable 成員控制的,這是一個布爾值,當reliable=1 時使用同步方式發布消息,意味著必須完成當前正在發布的消息(收到確認)之后才能發布另一個消息;如果reliable=0 則使用異步方式發布消息。
當使用MQTTClient_connectOptions_initializer 宏對MQTTClient_connectOptions 對象進行初始化時,
reliable 標志被初始化為1,所以默認是使用了同步方式。
返回值:成功返回MQTTCLIENT_SUCCESS,失敗返回MQTTCLIENT_FAILURE。
注意:調用MQTTClient_setCallbacks 函數設置回調必須在連接服務器之前完成!
使用示例:
static void delivered(void *context, MQTTClient_deliveryToken dt) {printf("Message with token value %d delivery confirmed\n", dt); } static int msgarrvd(void *context, char *topicName, int topicLen,MQTTClient_message *message) {printf("Message arrived\n");printf("topic: %s\n", topicName);printf("message: <%d>%s\n", message->payloadlen, (char *)message->payload);MQTTClient_freeMessage(&message); //釋放內存MQTTClient_free(topicName); //釋放內存return 1; } static void connlost(void *context, char *cause) {printf("\nConnection lost\n");printf(" cause: %s\n", cause); } int main(void) {....../* 設置回調*/if (MQTTCLIENT_SUCCESS !=(rc = MQTTClient_setCallbacks(client, NULL, connlost,msgarrvd, delivered))) {printf("Failed to set callbacks, return code %d\n", rc);return EXIT_FAILURE;}...... }對于msgarrvd 函數有兩個點需要注意:
? 退出函數之前需要釋放消息的內存空間,必須調用MQTTClient_freeMessage 函數;同時也要釋放主題名稱占用的內存空間,必須調用MQTTClient_free。
? 函數的返回值。此函數的返回值必須是0 或1,返回1 表示消息已經成功處理;返回0 則表示消息處理存在問題,在這種情況下,客戶端庫將重新調用MQTTClient_messageArrived()以嘗試再次將消息傳遞給客戶端應用程序,所以返回0 時不要釋放消息和主題所占用的內存空間,否則重新投遞失敗。
發布消息
當客戶端成功連接到服務端之后,便可以發布消息或訂閱主題了,應用程序通過MQTTClient_publishMessage庫函數來發布一個消息:
int MQTTClient_publishMessage(MQTTClient handle,const char *topicName,MQTTClient_message *msg,MQTTClient_deliveryToken *dt );handle:客戶端句柄;
topicName:主題名稱。向該主題發布消息。
msg:指向一個MQTTClient_message 對象的指針。
dt:返回給應用程序的傳遞令牌。
返回值:成功返回MQTTCLIENT_SUCCESS,失敗返回錯誤碼。
使用示例
MQTTClient_message pubmsg = MQTTClient_message_initializer; MQTTClient_deliveryToken token; ...... /* 發布消息*/ pubmsg.payload = "online"; //消息內容 pubmsg.payloadlen = 6; //消息的長度 pubmsg.qos = 0; //QoS 等級 pubmsg.retained = 1; //消息的保留標志 if (MQTTCLIENT_SUCCESS != (rc = MQTTClient_publishMessage(client, "dt2914/testTopic", &pubmsg, &token))) {printf("Failed to publish message, return code %d\n", rc);return EXIT_FAILURE; }訂閱主題和取消訂閱主題
客戶端應用程序調用MQTTClient_subscribe 函數來訂閱主題:
int MQTTClient_subscribe(MQTTClient handle,const char *topic,int qos );handle:客戶端句柄;
topic:主題名稱。客戶端訂閱的主題。
qos:QoS 等級。
返回值:成功返回MQTTCLIENT_SUCCESS,失敗返回錯誤碼。
使用示例
...... if (MQTTCLIENT_SUCCESS !=(rc = MQTTClient_subscribe(client, "dt2914/testTopic", 0))) {printf("Failed to subscribe, return code %d\n", rc);return EXIT_FAILURE; } ......當客戶端想取消之前訂閱的主題時,可調用MQTTClient_unsubscribe 函數,如下所示:
int MQTTClient_unsubscribe(MQTTClient handle,const char *topic );handle:客戶端句柄;
topic:主題名稱。取消訂閱該主題。
返回值:成功返回MQTTCLIENT_SUCCESS,失敗返回錯誤碼。
斷開服務端連接
當客戶端需要主動斷開與客戶端連接時,可調用MQTTClient_disconnect 函數:
int MQTTClient_disconnect(MQTTClient handle,int timeout );handle:客戶端句柄;
timeout:超時時間。客戶端將斷開連接延遲最多timeout 時間(以毫秒為單位),以便完成正在進行中的消息傳輸。
返回值:如果客戶端成功從服務器斷開連接,則返回MQTTCLIENT_SUCCESS;如果客戶端無法與服務器斷開連接,則返回錯誤代碼。
編寫客戶端程序
上小節介紹一些基本的MQTT 客戶端庫函數,除了這些基本API 之外,MQTT 客戶端庫還提供了其它很多的API。
本小節將使用上面介紹的幾個API 來編寫一個自己的MQTT 客戶端應用程序,然后使其在我們的開發板上運行,實現自己的私人物聯網項目。
功能設計如下:
? 基于然也物聯平臺提供的社區版MQTT 服務器實現個人物聯網小項目;
? 用戶可通過手機或電腦遠程控制開發板上的一顆LED 燈;
? 開發板客戶端每隔30 秒向服務端發送SoC 當前的溫度值,用戶通過手機或電腦可查看到該溫度值。
示例程序筆者已經給大家寫好了,由于功能比較簡單,所以代碼比較短,只有一個源文件;雖然只有一個源文件,為了學以致用,我們將使用cmake 來構建這個小項目。工程目錄結構如下所示:
mqtt_prj 工程對應的路徑為:開發板光盤->11、Linux C 應用編程例程源碼->33_mqtt->mqtt_prj。
arm-linux-setup.cmake 文件
arm-linux-setup.cmake 源文件用于配置cmake 交叉編譯,其內容如下所示:
這個就不多說了,大家需要根據自己的交叉編譯器的實際安裝路徑進行修改。
CMakeLists.txt 文件
文件內容如下所示:
客戶端應用程序源文件mqttClient.c
接下來我們看看客戶端源碼mqttClient.c 的內容,如下所示:
代碼中的三個主題說明一下:
WILL_TOPIC:這是客戶端的遺囑主題。
LED_TOPIC:LED 主題,我們的開發板客戶端訂閱了該主題,而我們會通過其它客戶端,譬如手機或電腦去向這個主題發布信息,那么接收到信息之后根據信息的內容,來對LED 做出相應的控制,譬如點亮LED、熄滅LED。
TEMP_TOPIC:溫度主題,我們的開發板客戶端會向這個主題發布消息,這個消息的內容就是開發板這個芯片溫度值,開發板的溫度值怎么獲取?對于我們MX6U 開發板來說,就是讀取
/sys/class/thermal/thermal_zone0/temp 屬性文件。同樣,其它客戶端(譬如手機或電腦)會訂閱這個溫度主題,所以,手機或電腦就會收到開發板的溫度信息。程序中是設置30 秒發一次。
構建、編譯
我們直接進行編譯,進入到工程目錄下的build 目錄中,執行cmake 構建:
執行make 編譯:
編譯成功之后,在build/bin 目錄下生成了可執行文件mqttClient。
將可執行文件mqttClient 拷貝到開發板Linux 系統/home/root 目錄下。
演示
在進行測試之前,確保開發板是能夠上網的,也就是能夠連接外網,注意不是局域網。執行mqttClient 客戶端應用程序,如下所示:
這樣,我們的開發板作為MQTT 客戶端就成功連接上了MQTT 服務器,并且每隔30 秒向服務器發布溫度信息,同樣也會接收LED 主題的信息。
現在我們使用MQTT.fx 在電腦上進行測試,打開MQTT.fx 客戶端軟件,使用然也物聯工作人員發給你的8 組客戶端連接認證信息中的其中一組(不要使用開發板客戶端已經使用的那組)去連接然也物聯社區版MQTT 服務器:
接下來我們訂閱溫度主題"dt_mqtt/temperature",你需要修改成你的個人專屬主題級別,也就是將dt_mqtt換成你的個人專屬主題級別。訂閱之后立馬就會收到溫度信息,并且之后每隔30 秒會收到開發板客戶端發布的溫度信息,如下圖所示:
接著我們向LED 主題“dt_mqtt/led”(同樣你也需要修改成你的個人專屬主題級別,也就是將dt_mqtt換成你的個人專屬主題級別)發布消息去控制開發板上的LED:
我這里沒法給你演示,你自己看效果,有沒有成功控制板上的LED 燈,反正筆者這里是沒有問題的。
除了使用電腦之外,我們還可以使用手機控制或查看開發板芯片溫度信息,在手機上使用MQTTool 軟件連接然也物聯社區版MQTT 服務器(同樣也是使用8 組連接認證信息中的其中一組,不要使用開發板和MQTT.fx 正在使用的這組信息),連接成功之后訂閱溫度主題查看開發板溫度信息、向LED 主題發布信息控制開發板上的LED 燈,如下所示:
總結
以上是生活随笔為你收集整理的MQTT 物联网协议的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Tomcat7.0+的JNDI问题
- 下一篇: 深入分析C++引用