當前位置:首頁 » 密碼管理 » mqttssl加密

mqttssl加密

發布時間: 2022-12-06 01:35:35

『壹』 3. MQTT簡要介紹

——

[1.MQTT項目工程](https://github.com/LiamBindle/MQTT-C)

[2.MQTT API說明文檔](https://liambindle.ca/MQTT-C/group__api.html)

[3.MQTT協議中文版](https://mcxiaoke.gitbooks.io/mqtt-cn/content/mqtt/01-Introction.html)

MQTT是一個客戶端服務端架構的發布/訂閱模式的消息傳輸協議。它的設計思想是輕巧、開放、簡單、規范,易於實現。這些特點使得它對很多場景來說都是很好的選擇,特別是對於受限的環境如機器與機器的通信(M2M)以及物聯網環境(IoT)。

MQTT協議通過交換預定義的MQTT控制報文來通信。

報文格式: 固定包頭+可變包頭+payload。

固定包頭: 由兩個位元組組成

                byte1 高四位表示控制報文的類型,低四位表示控制報文類型的標志位。

                byte2表示剩餘長度,包括可變報頭和負載的數據。剩餘長度不包括用於編碼剩餘長度欄位本身的位元組數。

可變包頭:

                某些MQTT控制報文包含一個可變報頭部分。它在固定報頭和負載之間。可變報頭的內容根據報文類型的不同而不同。可變報頭的報文標(Packet Identifier)欄位存在於在多個類型的報文里。

有效載荷:

                對於PUBLISH來說有效載荷就是應用消息。

——1. 網路建立連接後,客戶端發送的第一個報文必須是CONNECT報文

——2. 客戶端只能發送一次CONNECT 報文,發送兩次會當作協議違規處理,並斷開連接。

——3. 有效載荷包括:客戶端唯一標識符,will主題,will消息,用戶名和密碼。

——4.可變包頭:協議名(protocol name)+協議級別(protocol level)+連接標志(connect flags)+保持連接(keep alive)。

——5. 協議名: MQTT的UTF-8編碼的字元串。(MSB+LSB +MQTT 六個位元組)

——6. 協議級別:一個位元組

——7. 連接標志:一個位元組,包含一些用於指定MQTT連接行為的參數。同時還指出有效載荷中的欄位是否存在。服務端必須驗證CONNECT控制報文的保留標志位(第0位)是否為0,如果不為0必須斷開客戶端連接。

——8. 清理會話:byte8 的bit1位標志。

        這個二進制位指定了會話狀態的處理方式。客戶端和服務端可以保存會話狀態,以支持跨網路連接的可靠消息傳輸。這個標志位用於控

       制會話狀態的生存時間。

        標志設置為0:服務端必須基於當前會話(使用客戶端標識符識別)的狀態恢復與客戶端的通信。

        標志設置為1:客戶端和服務端必須丟棄之前的任何會話並開始一個新的會話。會話僅持續和網路連接同樣長的時間。

——9. 遺囑標志 WILL FLAG: byte8的bit2位標志。

        遺囑標志(Will Flag)被設置為1,表示如果連接請求被接受了,遺囑(Will Message)消息必須被存儲在服務端並且與這個網路連接關聯。之後網路連接關閉時,服務端必須發布這個遺囑消息,除非服務端收到DISCONNECT報文時刪除了這個遺囑消息。

服務端發送CONNACK報文響應從客戶端收到的CONNECT報文。服務端發送給客戶端的第一個報文必須是CONNACK。

如果客戶端在合理的時間內沒有收到服務端的CONNACK報文,客戶端應該關閉網路連接。合理 的時間取決於應用的類型和通信基礎設施。

CONNACK報文沒有有效載荷。

PUBLISH控制報文是指從客戶端向服務端或者服務端向客戶端傳輸一個應用消息。

固定包頭:

注意byte1的bit3為重發標志DUP。

如果DUP標志被設置為0,表示這是客戶端或服務端第一次請求發送這個PUBLISH報文。如果DUP標志被設置為1,表示這可能是一個早前報文請求的重發。

服務端發送PUBLISH報文給訂閱者時,收到(入站)的PUBLISH報文的DUP標志的值不會被傳播。發送(出站)的PUBLISH報文與收到(入站)的PUBLISH報文中的DUP標志是獨立設置的,它的值必須單獨的根據發送(出站)的PUBLISH報文是否是一個重發來確定。

可變包頭:

主題名 :topic name

報文標識符 :packet identitfier。

有效載荷 :有效載荷包含將被發布的應用消息。數據的內容和格式是應用特定的。有效載荷的長度這樣計算:用固定報頭中的剩餘長度欄位的值減去可變報頭的長度。包含零長度有效載荷的PUBLISH報文是合法的。

響應:PUBLISH報文的接收者必須按照根據PUBLISH報文中的QoS等級發送響應。

PUBACK報文是對QoS 1等級的PUBLISH報文的響應。

PUBACK報文沒有有效載荷。

PUBREC報文是對QoS等級2的PUBLISH報文的響應。它是QoS 2等級協議交換的第二個報文。

PUBREC報文沒有有效載荷。

PUBREL報文是對PUBREC報文的響應。它是QoS 2等級協議交換的第三個報文。

PUBREL報文沒有有效載荷。

PUBCOMP報文是對PUBREL報文的響應。它是QoS 2等級協議交換的第四個也是最後一個報文。

PUBCOMP報文沒有有效載荷。

客戶端向服務端發送 SUBSCRIBE 報文用於創建一個或多個訂閱。每個訂閱注冊客戶端關心的一個或多個主題。為了將應用消息轉發給與那些訂閱匹配的主題,服務端發送PUBLISH報文給客戶端。SUBSCRIBE報文也(為每個訂閱)指定了最大的QoS等級,服務端根據這個發送應用消息給客戶端。

有效載荷:

SUBSCRIBE報文的有效載荷包含了一個主題過濾器列表,它們表示客戶端想要訂閱的主題。SUBSCRIBE報文的有效載荷必須包含至少一對主題過濾器 和 QoS等級欄位組合。沒有有效載荷的SUBSCRIBE報文是違反協議的。

響應:

服務端收到客戶端發送的一個SUBSCRIBE報文時,必須使用SUBACK報文響應,SUBACK報文必須和等待確認的SUBSCRIBE報文有相同的報文標識符。

服務端發送SUBACK報文給客戶端,用於確認它已收到並且正在處理SUBSCRIBE報文。SUBACK報文包含一個返回碼清單,它們指定了SUBSCRIBE請求的每個訂閱被授予的最大QoS等級。

有效載荷:

有效載荷包含一個返回碼清單。每個返回碼對應等待確認的SUBSCRIBE報文中的一個主題過濾器。返回碼的順序必須和SUBSCRIBE報文中主題過濾器的順序相同。

客戶端發送UNSUBSCRIBE報文給服務端,用於取消訂閱主題。

有效載荷 :

UNSUBSCRIBE報文的有效載荷包含客戶端想要取消訂閱的主題過濾器列表。

UNSUBSCRIBE報文中的主題過濾器必須是連續打包的、按照定義的UTF-8編碼字元串 

UNSUBSCRIBE報文的有效載荷必須至少包含一個消息過濾器。沒有有效載荷的UNSUBSCRIBE報文是違反協議的。

響應:

UNSUBSCRIBE報文提供的主題過濾器(無論是否包含通配符)必須與服務端持有的這個客 戶端的當前主題過濾器集合逐個字元比較。如果有任何過濾器完全匹配,那麼它(服務端)自己的訂閱將被刪除,否則不會有進一步的處理。

如果服務端刪除了一個訂閱:

——它必須停止分發任何新消息給這個客戶端 []。

——它必須完成分發任何已經開始往客戶端發送的QoS 1和QoS 2的消息 []。

——它可以繼續發送任何現存的准備分發給客戶端的緩存消息。

            服務端必須發送UNSUBACK報文響應客戶端的UNSUBSCRIBE請求。UNSUBACK報文必須包含和UNSUBSCRIBE報文相同的報文標識符 。即使沒有刪除任何主題訂閱,服務端也必須發送一個UNSUBACK響應 。

            如果服務端收到包含多個主題過濾器的UNSUBSCRIBE報文,它必須如同收到了一系列的多個UNSUBSCRIBE報文一樣處理那個報文,除了將它們的響應合並到一個單獨的UNSUBACK報文外。 

服務端發送UNSUBACK報文給客戶端用於確認收到UNSUBSCRIBE報文。

UNSUBACK報文沒有有效載荷。

客戶端發送PINGREQ報文給服務端的。用於:

1. 在沒有任何其它控制報文從客戶端發給服務的時,告知服務端客戶端還活著。

2. 請求服務端發送 響應確認它還活著。

3. 使用網路以確認網路連接沒有斷開。

保持連接(Keep Alive)處理中用到這個報文。

——PINGREQ報文沒有可變報頭。

——PINGREQ報文沒有有效載荷。

響應:

服務端必須發送 PINGRESP報文響應客戶端的PINGREQ報文。

服務端發送PINGRESP報文響應客戶端的PINGREQ報文。表示服務端還活著。

保持連接(Keep Alive)處理中用到這個報文。

PINGRESP報文沒有可變報頭。

PINGRESP報文沒有有效載荷。

DISCONNECT報文是客戶端發給服務端的最後一個控制報文。表示客戶端正常斷開連接。

DISCONNECT報文沒有可變報頭。

DISCONNECT報文沒有有效載荷。

響應:

客戶端發送DISCONNECT報文之後:

—— 必須關閉網路連接 [MQTT-3.14.4-1]。

——不能通過那個網路連接再發送任何控制報文。

服務端在收到DISCONNECT報文時:

——必須丟棄任何與當前連接關聯的未發布的遺囑消息。

——應該關閉網路連接,如果客戶端 還沒有這么做。

為了提供服務質量保證,客戶端和服務端有必要存儲會話狀態。在整個會話期間,客戶端和服務端都必須存儲會話狀態 。會話必須至少持續和它的活躍網路連接同樣長的時間。服務端的保留消息不是會話狀態的組成部分。服務端應該保留那種消息直到客戶端刪除它。

MQTT協議要求基礎傳輸層能夠提供有序的、可靠的、雙向傳輸(從客戶端到服務端 和從服務端到客戶端)的位元組流。

無連接的網路傳輸協議如UDP是不支持的,因為他們可能會丟失數據包或對數據包重排序。

MQTT按照這里定義的服務質量 (QoS) 等級分發應用消息。分發協議是對稱的,在下面的描述中,客戶端和服務端既可以是發送者也可以是接收者。分發協議關注的是從單個發送者到單個接收者的應用消息。服務端分發應用消息給多個客戶端時,每個客戶端獨立處理。分發給客戶端的出站應用消息和入站應用消息的QoS等級可能是不同的。

qos0:最多分發一次

qos1:至少分發一次

qos2:僅分發一次

客戶端設置清理會話(CleanSession)標志為0重連時,客戶端和服務端必須使用原始的報文標識符重發任何未確認的PUBLISH報文(如果QoS>0)和PUBREL報文 [MQTT-4.4.0-1]。這是唯一要求客戶端或服務端重發消息的情況。

服務端接管入站應用消息的所有權時,它必須將消息添加到訂閱匹配的客戶端的會話狀態。正常情況下,客戶端收到發送給它的訂閱的消息。客戶端也可能收到不是與它的訂閱精確匹配的消息。如果服務端自動給客戶端分配了一個訂閱,可能發生這種情況。正在處理UBSUBSCRIBE請求時也可能收到消息。客戶端必須按照可用的服務質量(QoS)規則確認它收到的任何PUBLISH報文,不管它選擇是否處理報文包含的應用消息 。

實現本章定義的協議流程時,客戶端必須遵循下列規則:

重發任何之前的PUBLISH報文時,必須按原始PUBLISH報文的發送順序重發(適用於QoS 1和QoS 2消息)[MQTT-4.6.0-1]。

——必須按照對應的PUBLISH報文的順序發送PUBACK報文(QoS 1消息)。

——必須按照對應的PUBLISH報文的順序發送PUBREC報文(QoS 2消息。

——必須按照對應的PUBREC報文的順序發送PUBREL報文(QoS 2消息)。

服務端必須默認認為每個主題都是有序的。它可以提供一個管理功能或其它機制,以允許將一個或多個主題當作是無序的 。

服務端處理發送給有序主題的消息時,必須按照上面的規則將消息分發給每個訂閱者。此外,它必須按照從客戶端收到的順序發送PUBLISH報文給消費者(對相同的主題和QoS)。

斜杠(『/』 U+002F)用於分割主題的每個層級,為主題名提供一個分層結構.

數字標志(『#』 U+0023)是用於匹配主題中任意層級的通配符。

加號 (『+』 U+002B) 是只能用於單個主題層級匹配的通配符。

服務端不能將 $ 字元開頭的主題名匹配通配符 (#或+) 開頭的主題過濾器.

$SYS/ 被廣泛用作包含伺服器特定信息或控制介面的主題的前綴。

應用不能使用 $ 字元開頭的主題。

訂閱 「#」 的客戶端不會收到任何發布到以 「$」 開頭主題的消息。

訂閱 「+/monitor/Clients」 的客戶端不會收到任何發布到 「$SYS/monitor/Clients」 的消息。

訂閱 「$SYS/#」 的客戶端會收到發布到以 「$SYS/」 開頭主題的消息。

訂閱 「$SYS/monitor/+」 的客戶端會收到發布到 「$SYS/monitor/Clients」 主題的消息。

如果客戶端想同時接受以 「$SYS/」 開頭主題的消息和不以 $ 開頭主題的消息,它需要同

時訂閱 「#」 和 「「$SYS/#」。

除非另有說明,如果服務端或客戶端遇到了協議違規的行為,它必須關閉傳輸這個協議違規控制報文的網路連接.

MQTT方案通常部署在不安全的通信環境中。在這種情況下,協議實現通常需要提供這些機制:

——用戶和設備身份認證

——服務端資源訪問授權

——MQTT控制報文和內嵌應用數據的完整性校驗

——MQTT控制報文和內嵌應用數據的隱私控制

作為傳輸層協議,MQTT僅關注消息傳輸,提供合適的安全功能是實現者的責任。使用TLS[RFC5246] 是比較普遍的選擇。

廣泛採用高級加密標准 [AES] 數據加密標准 [DES] 。

推薦使用為受限的低端設備特別優化過的輕量級加密國際標准 ISO 29192 [ISO29192] 。

如果MQTT在WebSocket [RFC6455] 連接上傳輸,必須滿足下面的條件:

——MQTT控制報文必須使用WebSocket二進制數據幀發送。如果收到任何其它類型的數據幀,接收者必須關閉網路連接 。

——單個WebSocket數據幀可以包含多個或者部分MQTT報文。接收者不能假設MQTT控制報文按WebSocket幀邊界對齊 。

——客戶端必須將字元串 mqtt 包含在它提供的WebSocket子協議列表裡 。

——服務端選擇和返回的WebSocket子協議名必須是  。

——用於連接客戶端和伺服器的WebSocket URI對MQTT協議沒有任何影響。

MQTT規范定義了MQTT客戶端實現和MQTT服務端實現的一致性要求

MQTT實現可以同時是MQTT客戶端和MQTT服務端。接受入站連接和建立到其它服務端的出站連接的服務端必須同時符合MQTT客戶端和MQTT服務端的要求 。

為了與任何其它的一致性實現交互操作,一致性實現不能要求使用在本規范之外定義的任何擴展 。

附錄:

控制報文類型

byte1:標志位flags:

『貳』 如何在 JMeter 中使用 MQTT 插件

JMeter 內置 HTTP/HTTPS、TCP 等支持多種協議,還具備插件擴展機制。

MQTT 協議作為物聯網界的主流協議,雖然並非 JMeter 自帶的協議類型,但在物聯網測試場景中極為普遍。為了支持 MQTT 協議的規模測試,EMQ 映雲科技開發了基於 JMeter 的 MQTT 協議開源測試插件: https://github.com/xmeter-net/mqtt-jmeter 。

經過幾個版本的迭代,目前 JMeter MQTT 插件的最新版本為 2.0.2,支持連接、消息發布、消息訂閱等多種采樣器,並可通過組合構建更復雜的測試場景。

本文我們將具體介紹如何在 JMeter 中使用 MQTT 插件。

MQTT 插件的安裝方式與其他 JMeter 第三方插件類似。

連接采樣器模擬物聯網設備,發起 MQTT 連接。

Server name or IP: 指向被測 MQTT 伺服器地址

Port number: 以 EMQ X 為例,默認 TCP 連接的埠是 1883, SSL 連接則是 8883。具體的埠請參照伺服器的具體配置。

MQTT version : 目前支持 MQTT 3.1及3.1.1版本。

Timeout: 連接超時設置,以秒為單位。

Protocols: 支持TCP、SSL、WS 和 WSS 方式連接 MQTT 伺服器。當選擇 SSL 或 WSS 加密通道連接時,可以選擇單向或者雙向認證(Dual)。如果希望進行雙向認證,還需要指定相應的客戶端證書(p12證書),以及對應的文件保護密碼(Secret)。

User authentication: 如果 MQTT 伺服器配置了用戶認證,需要提供相應的用戶名( User name )和密碼( Password )。

ClientId: 虛擬用戶的標識。如果勾選了「Add random suffix for ClientId」,將會在 ClientId 的基礎上給每個虛擬用戶再添加一個 uuid 串作為後綴,整個作為虛擬用戶標識。

Keep alive(s): 心跳信號發送間隔。例如,300 表示客戶端每隔 300 秒向伺服器發出 ping 請求,以保持連接活躍。

Connect attempt max: 第一次連接過程中,嘗試重連的最大次數。超過該次數則認為連接失敗。如果希望一直嘗試重連,可以設為 -1。

Reconnect attempt max: 後繼連接過程中,嘗試重連的最大次數。超過該次數則認為連接失敗。如果希望一直嘗試重連,可以設為 -1。

Clean session : 如果希望在連接之間保留會話狀態,可以將該選項設為 false。如果不希望在新的連接中保留會話狀態,則將該項設為true。

消息發布采樣器復用連接采樣器中建立的 MQTT 連接,向目標 MQTT 伺服器發布消息。

QoS Level: 服務質量,取值為 0,1,2,分別代表 MQTT 協議規范里的至多一次(AT_MOST_ONCE),至少一次(AT_LEAST_ONCE),精確一次(EXACTLY_ONCE)

Retained messages : 如果希望使用「保留消息」,可將該選項設為 true,MQTT 伺服器端將會存儲插件發布的保留消息及其 QoS,並在相應 topic 上發生訂閱時,直接將最後一條保留消息投遞給訂閱端,使得訂閱端不必等待即可獲取發布端的最新狀態值。

Topic name: 發布消息所屬的主題。

Add timestamp in payload: 如果勾選,發布的消息體開頭會附帶當前時間戳,配合消息訂閱采樣器的 Payload includes timestamp 選項,可以在消息接收端計算消息達到的延時。如果不勾選則只發送實際的消息體。

Payloads Message type: 目前支持三種消息類型

消息發布采樣器復用連接采樣器中建立的 MQTT 連接,從目標 MQTT 伺服器上訂閱消息。

QoS Level: 服務質量,含義與消息發布采樣器相同。

Topic name(s): 訂閱消息所屬的主題。支持單個消息訂閱采樣器訂閱多個主題,主題之間用逗號分隔。

Payload includes timestamp: 如果勾選,會從消息體開頭處解析發送時間戳,配合消息發布采樣器的 Add timestamp in payload 選項,可以用於計算消息的接收延時。如果不勾選則只解析實際的消息體。

Sample on : 采樣方式,默認為" specified elapsed time(ms) ",即每隔指定的毫秒時間采樣一次。也可以選擇" number of received messages ",即每接收到指定的消息數采樣一次。

Debug response: 如果勾選,消息內容會列印在 JMeter 的響應結果中。該選項主要用於調試目的,正式運行測試不建議勾選,以免影響測試效率。

斷開連接采樣器中建立的 MQTT 連接。

本文我們介紹了 JMeter MQTT 插件的各測試組件,在下期文章中我們將針對不同的測試場景詳細介紹如何用 MQTT 插件來構建測試腳本

『叄』 5-Openwrt MQTT client使用

在mosquitto裡面有個client目錄,裡面就是使用libmosquitto實現的客戶端程序,封裝成mosquitto_sub和mosquitto_pub命令行。

所以新建一個跟client同一級,自己的client,添加對應的文件

Makefile的內容

main.c的內容

myclient.h的內容

外層的mosquitto/src/Makefile裡面添加myclient文件的編譯

編譯測試一切正常,接下去添加mqtt的內容

mqtt client裡面最主要的就是幾個回調函數,先調用lib_init,正常後,就這只各個callback,然後在callback裡面做邏輯。

各回調函數的內容

邏輯應該也比較直觀,當connect成功後,在回調函數裡面訂閱test1主題的內容,然後發布test2主題的內容。

收到內容就在 myclient_message_callback 回調裡面列印處理。

正常情況我們都會讓客戶端的連接做一些賬號密碼的設置,避免別人攻擊。

將allow_anonymous改成不允許匿名登陸,並制定pwfile。

vim /etc/mosquittoConf/mosquitto.conf

在ubuntu上面使用mosquitto_passwd生成密碼

就會在pwfile文件下生成賬號和加密的密碼root/admin

這是後登陸的時候就需要-u root -P admin進行登陸

mosquitto提供了mosquitto_passwd命令來生成賬號密碼等,不過這個方式不喜歡,因為沒辦法定製化自己想要的賬號密碼加密方式,所以做了一些小改動。

在myclient裡面加我們想要的加密方式,然後在mosquitto broker的源碼裡面添加對應的解密方式即可。

如下,賬號為client_name,然後通過rsa和base64生成密碼,myclient的試下調用 mosquitto_username_pw_set 函數。

然後在mosquitto broker裡面添加解密,位於mosquitto/src/security.c文件的 mosquitto_unpwd_check 函數裡面。

另一個加密方式就是SSL認證,給客戶端提供相應的證書,和配置協議(mqtt or websockets)一樣,在配置文件監聽的埠下面可以添加ssl的配置選項,每個port都可以單獨配置ssl的證書內同容。

如下:從埠7885連接進來的設備需要下面的證書要求

設備的認證有單向認證和雙向認證兩種:

單向認證,只需要提供ca證書

雙向認證,需要ca,pem,key三個

按步驟一步一步執行,生成證書(裡面也可以指定各參數,有效時間):

按上面的步驟可以生成如下文件

在伺服器端需要放三個文件

如果是單向認證,客戶端只需要一個文件

如果是雙向認證,客戶端只需要三個文件

查看證書的有效時間

『肆』 TI CC3200的MQTT怎麼使用ssl

MQTT連接建立的代碼(SSL方式)

[java] view plain
public static void connect(Driver driver) {
ServerConfig serverConfig = UserMole.Instance.getServerConfig();
MqttConnectOptions conOpt = new MqttConnectOptions();
try {
SSLContext sslContext;
KeyStore ts = KeyStore.getInstance("BKS");
ts.load(context.getResources().openRawResource(R.raw.test_cert),
"123456".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(ts);
TrustManager[] tm = tmf.getTrustManagers();
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tm, null);

SocketFactory factory = sslContext.getSocketFactory();
conOpt.setSocketFactory(factory);
} catch (Exception e) {
e.printStackTrace();
}

[java] view plain
//paho庫得
Iterator<Map.Entry> it = Connections
.getInstance(context).getConnections().entrySet().iterator();
while (it.hasNext()) {
MqttClientAndroidService detectClient = it.next().getValue()
.getClient();
try {
detectClient.disconnect();
} catch (MqttException e) {
e.printStackTrace();
}
it.remove();
}

// The basic client information
MqttClientAndroidService client;
client = Connections.getInstance(context).createClient(context,
serverConfig.getUri(), serverConfig.clientId);
Integer qos = Integer.parseInt(context.getResources().getString(
R.string.qos));
Boolean retained = Boolean.parseBoolean(context.getResources()
.getString(R.string.retained));

// connection options
int timeout = Integer.parseInt(context.getResources().getString(
(R.string.timeout)));
int keepalive = Integer.parseInt(context.getResources().getString(
R.string.keepalive));

Connection connection = new Connection(serverConfig.getClientHandle(),
serverConfig.clientId, serverConfig.server,
Integer.parseInt(serverConfig.port), context, client,
serverConfig.ssl);

// connection.registerChangeListener(changeListener);
// connect client

String[] actionArgs = new String[1];
actionArgs[0] = serverConfig.clientId;
connection.changeConnectionStatus(ConnectionStatus.CONNECTING);

boolean cleanSession = false;
conOpt.setCleanSession(cleanSession);
conOpt.setConnectionTimeout(timeout);
conOpt.setKeepAliveInterval(keepalive);
if (!TextUtils.isEmpty(serverConfig.user)) {
conOpt.setUserName(serverConfig.user);
}
if (!TextUtils.isEmpty(serverConfig.pwd)) {
conOpt.setPassword(serverConfig.pwd.toCharArray());
}
// conOpt.setPassword("1111".toCharArray());
final ActionListener callback = new ActionListener(context,
ActionListener.Action.CONNECT, driver.getMqttUtilsCallback(),
serverConfig.getClientHandle(), actionArgs);

boolean doConnect = true;

String message = ActivityConstants.message;
String topic = ActivityConstants.topic;

if ((!TextUtils.isEmpty(message) || !TextUtils.isEmpty(topic))) {
// need to make a message since last will is set
try {
conOpt.setWill(topic, message.getBytes(), qos.intValue(),
retained.booleanValue());
} catch (Exception e) {
e.printStackTrace();
doConnect = false;
callback.onFailure(null, e);
}
}
client.setCallback(new MqttCallbackHandler(context, serverConfig
.getClientHandle(), driver));

connection.addConnectionOptions(conOpt);
Connections.getInstance(context).addConnection(connection);
if (doConnect) {
try {
client.connect(conOpt, null, callback);
} catch (MqttException e) {
Log.e(TAG, "MqttException Occured", e);
}
}
}
發布(publish)代碼
[java] view plain
public static void publish(String clientHandle, String topic,
JSONObject jsonObj, int qos) {
MqttClientAndroidService client = Connections.getInstance(context)
.getConnection(clientHandle).getClient();
if (!isConnected(context)) {
try {
client.connect();
} catch (MqttException e) {
e.printStackTrace();
}
Toast.makeText(context, "please try again", Toast.LENGTH_SHORT)
.show();
return;
}
if (topic == null) {
Toast.makeText(context, "can not get other's identity for now",
Toast.LENGTH_SHORT).show();
return;
}
String[] args = new String[2];
if (jsonObj.optLong("time") != 0) {
args[0] = String.valueOf(jsonObj.optLong("time"));
args[1] = topic;
}

Boolean retained = Boolean.parseBoolean(context.getResources()
.getString(R.string.retained));
try {
client.publish(topic, jsonObj.toString().getBytes(), qos, retained,
null, new ActionListener(context, Action.PUBLISH, null,
clientHandle, args));
} catch (MqttSecurityException e) {
Log.e(TAG,
"Failed to publish a messged from the client with the handle "
+ clientHandle, e);
} catch (MqttException e) {
Log.e(TAG,
"Failed to publish a messged from the client with the handle "
+ clientHandle, e);
}
}
訂閱(subscribe)代碼
[java] view plain
public static void subscribe(MqttUtilsCallback mqttUtilsCallback,
String clientHandle, String topic) {
MqttClientAndroidService client = Connections.getInstance(context)
.getConnection(clientHandle).getClient();
if (client == null || (client != null && !client.isConnected())) {
Toast.makeText(context, "please connect to server first",
Toast.LENGTH_SHORT).show();
return;
}

if (TextUtils.isEmpty(topic)) {
topic = "hello";
}
String[] topics = new String[1];
topics[0] = topic;
try {
client.subscribe(topic, 1, null, new ActionListener(context,
ActionListener.Action.SUBSCRIBE, mqttUtilsCallback,
clientHandle, topics));
} catch (MqttSecurityException e) {
Log.e(TAG, "Failed to subscribe to" + topic
+ " the client with the handle " + clientHandle, e);
} catch (MqttException e) {
Log.e(TAG, "Failed to subscribe to" + topic
+ " the client with the handle " + clientHandle, e);
}

『伍』 1-MQTT基礎知識

由於物聯網的環境是非常特別的,所以MQTT遵循以下設計原則:

MQTT 協議的中心是 MQTT 伺服器或代理 (broker) ,支持發布程序和訂閱程序進行訪問,如下圖所示

MQTT擁有14種不同的消息類型:

MQTT是通過主題對消息進行分類的,本質上就是一個UTF-8的字元串,不過可以通過反斜杠表示多個層級關系。主題並不需要創建,直接使用就是了。

主題還可以通過通配符進行過濾。其中,+可以過濾一個層級,而#只能出現在主題最後表示過濾任意級別的層級。

舉個例子:

building-b/floor-5:代表B樓5層的設備。
+/floor-5:代表任何一個樓的5層的設備。
building-b/#:代表B樓所有的設備。
注意,MQTT允許使用通配符訂閱主題,但是並不允許使用通配符廣播。

WILL主題也叫遺囑消息,是一個特殊的主題。

客戶端連接Broker的時候,附帶一個will主題和will主題對應的內容。

當客戶端與Broker斷開連接時,Broker將該WILL主題的內容發送給相關的訂閱者的遺囑消息,這樣訂閱者就知道該客戶端已經離線了。

以下情況下會發送 Will Message:

註:在客戶端正常調用 disconnect 方法之後並不會被發送。

為了滿足不同的場景,MQTT支持三種不同級別的服務質量(Quality of Service,QoS)為不同場景提供消息可靠性:

級別2所提供的不重不丟很多情況下是最理想的,不過往返多次的確認一定對並發和延遲帶來影響。

級別1提供的至少一次語義在日誌處理這種場景下是完全OK的,所以像Kafka這類的系統利用這一特點減少確認從而大大提高了並發。

級別0適合雞肋數據場景,基本就沒怎麼用了。

客戶端在連接的時候可以設置clean session,如果設置成true說明在設備離線後broker不保存,設置成false說明在設備離線後broker保存消息,等上線的時候就發送給他。

客戶端在連接Broker的時候,會指定心跳的時候。連接成功之後,客戶端就按照這個心跳時間定時發送心跳數據給Broker,如果Broker在1.5T時間內沒有收到客戶端的心跳數據,則判定改設備已經離線,發送WILL主題廣播告訴別人該設備已離線。

MQTT可以使用SSL加密方式傳輸,設備的認證有單向認證和雙向認證兩種:

MQTT除了有SSL加密之外,對於連接也有賬號密碼的授權,只要賬號密碼正確的才可以連接成功。

MQTT 協議和mosquitto: https://shaocheng.li/posts/2015/08/11/

物聯網網關協議比較MQTT 和 Modbus:
https://software.intel.com/pt-br/node/628992?language=es

『陸』 Paho Mqtt SSL連接時常見異常問題以及解決方案

在使用Mqtt的SSL方式連接時遇到了如下問題:

說明使用SSL連接配置中的TrustManager里的server驗證失敗,即服務端證書簽名時用的host和現在簽名的host不是同一個。如果是HTTPS,可以通過重寫hostNameVerifyer的方法來解決問題,網上有很多攻略
對於Mqtt連接時遇到這個問題,應該檢查是否pom.xml中依賴的版本有沖突。經實際驗證,paho的版本使用 較新 版本時會遇到這個問題(無論證書是正確/錯誤,均優先報no name mathing錯誤,推測是較低版本的校驗機制較弱)因此可以參考下面的搭配,使用較低版本跳過這個錯誤。因為暫未找到mqtt ssl連接時忽略host驗證的方法

對於Mqtt連接時遇到這個問題,應該檢查是否現在所使用的證書,和伺服器端的證書不是同一個。這一次就栽在這個問題上了

pom.xml文件中的依賴:

『柒』 android mqtt+ssl

你是不是用的最新版本的?可能是你的網路問題吧,我前段時間下載一直下不動,然後我去網吧下載的,用U盤拷回來解壓到原來的安裝位置就可以了

熱點內容
linuxcpumysql 發布:2024-04-24 05:10:40 瀏覽:902
如何才能使郵件伺服器高效穩定地工作 發布:2024-04-24 04:30:55 瀏覽:461
sql數字開頭的 發布:2024-04-24 04:29:17 瀏覽:61
c電梯調度演算法 發布:2024-04-24 04:15:34 瀏覽:785
郵件對稱加密 發布:2024-04-24 04:10:26 瀏覽:666
c語言讀一行 發布:2024-04-24 04:10:25 瀏覽:150
如何破解博客密碼 發布:2024-04-24 04:05:22 瀏覽:962
我的世界伺服器點不動 發布:2024-04-24 04:04:42 瀏覽:385
安卓小說怎麼導出 發布:2024-04-24 03:51:23 瀏覽:348
不用編譯安裝linux 發布:2024-04-24 03:50:00 瀏覽:630