上次已經簡單的談了一些MQTT協議的一些知識,今天就來就上次的知識具體的Java實現。
現在就來具體說說實現這一步吧。中間的時間也是有點久。
MQTT消息的發送和訂閱都是依賴MQTT服務器的,沒有MQTT服務器,你的客戶端是無法訂閱和發送消息的。所以在最開始的時候,可以選擇性的在你的電腦上面安裝一個MQTT服務器。MQTT服務器有很多,大家也可以在網上去找一些安裝教程,這裏因爲和我要講內容關係不大,所以不再累述。
MQTT協議中是沒有發送者和接收者·的概念,所有的連接都是用戶,所以一個MQTT連接既可以發送消息,也可以接收消息。就等於所有的連接都是客戶端。下面我的客戶端代碼也是如此,因爲公司這邊接收的信息先是要進行認證,認證成功後再接收有用的信息。這時,客戶端在根據設備的信息來控制網關上面的設備,達到遠程控制設備的目的。因爲要使用服務器來轉發消息,所以對於服務器的測試也是比較重要的,但是我使用的是公司的服務器,所以這一塊我的瞭解比較少。但是我這邊有一些工具,谷歌瀏覽器的插件MQTTLens。可能會幫助你。(需要翻閱牆體)
MQTT使用的庫也是有很多的,下面的網址也是列舉了MQTT支持的庫,有java的,也有c的。網址如下:https://github.com/mqtt/mqtt.github.io/wiki/libraries。因爲最開始我的接觸還是比較淺,使用的是:Fusesource mqtt-client。所以java的demo也是基於這個庫的,但是後來和spring整合的時候發現有一些問題,因爲spring支持的只有一個庫,就是Eclipse Paho Java。但是原理都是一樣的,大家可以自己去決定,我的簡單的demo代碼還是基於Fusesource mqtt-client。在下一篇Spring和MQTT整合中使用的是Eclipse Paho Java。
下面就說一說具體的思路,這邊我的代碼是基於公司的網關需求,所以先說一說公司網關的具體流程。首先,網關會一直髮送身份驗證消息,等待客戶端認證,客戶端認證通過後,會發送具體有用的信息。客戶端這時在根據網關信息發送控制命令,到達控制的目的。在這個過程中,客戶端有訂閱和發送,所以一個客戶端就練習了發送消息和訂閱消息。這就是公司的具體操作流程。下面就說一說代碼的流程。
運行時要使用jar包,也可使用maven,但是使用maven時要注意版本。
具體的jar包和maven依賴在網址:https://gitee.com/iots/mqtt-client
依賴爲:
<dependency>
<groupId>org.fusesource.mqtt-client</groupId>
<artifactId>mqtt-client</artifactId>
<version>1.12</version>
</dependency>
下面開始編寫demo
首先先要配置MQTT的一些配置,配置比較多,也很繁瑣。
主要是配置主機號和端口號,根據自己的配置編寫代碼,在配置其他的一些細節配置,主要是和連接有關的。
代碼如下:
// MQTT設置說明
// 設置主機號
mqtt.setHost("服務器地址和端口號");
// 用於設置客戶端會話的ID。在setCleanSession(false);被調用時,MQTT服務器利用該ID獲得相應的會話。此ID應少於23個字符,默認根據本機地址、端口和時間自動生成
mqtt.setClientId("876543210");
// 若設爲false,MQTT服務器將持久化客戶端會話的主體訂閱和ACK位置,默認爲true
mqtt.setCleanSession(false);
// 定義客戶端傳來消息的最大時間間隔秒數,服務器可以據此判斷與客戶端的連接是否已經斷開,從而避免TCP/IP超時的長時間等待
mqtt.setKeepAlive((short) 60);
// 服務器認證用戶名
mqtt.setUserName("admin");
// 服務器認證密碼
mqtt.setPassword("admin");
// 設置“遺囑”消息的話題,若客戶端與服務器之間的連接意外中斷,服務器將發佈客戶端的“遺囑”消息
mqtt.setWillTopic("willTopic");
// 設置“遺囑”消息的內容,默認是長度爲零的消息
mqtt.setWillMessage("willMessage");
// 設置“遺囑”消息的QoS,默認爲QoS.ATMOSTONCE
mqtt.setWillQos(QoS.AT_LEAST_ONCE);
// 若想要在發佈“遺囑”消息時擁有retain選項,則爲true
mqtt.setWillRetain(true);
// 設置版本
mqtt.setVersion("3.1.1");
// 失敗重連接設置說明
// 客戶端首次連接到服務器時,連接的最大重試次數,超出該次數客戶端將返回錯誤。-1意爲無重試上限,默認爲-1
mqtt.setConnectAttemptsMax(10L);
// 客戶端已經連接到服務器,但因某種原因連接斷開時的最大重試次數,超出該次數客戶端將返回錯誤。-1意爲無重試上限,默認爲-1
mqtt.setReconnectAttemptsMax(3L);
// 首次重連接間隔毫秒數,默認爲10ms
mqtt.setReconnectDelay(10L);
// 重連接間隔毫秒數,默認爲30000ms
mqtt.setReconnectDelayMax(30000L);
// 設置重連接指數迴歸。設置爲1則停用指數迴歸,默認爲2
mqtt.setReconnectBackOffMultiplier(2);
// Socket設置說明
// 設置socket接收緩衝區大小,默認爲65536(64k)
mqtt.setReceiveBufferSize(65536);
// 設置socket發送緩衝區大小,默認爲65536(64k)
mqtt.setSendBufferSize(65536);
// 設置發送數據包頭的流量類型或服務類型字段,默認爲8,意爲吞吐量最大化傳輸
mqtt.setTrafficClass(8);
// 帶寬限制設置說明
// 設置連接的最大接收速率,單位爲bytes/s。默認爲0,即無限制
mqtt.setMaxReadRate(0);
// 設置連接的最大發送速率,單位爲bytes/s。默認爲0,即無限制
mqtt.setMaxWriteRate(0);
// 選擇消息分發隊列
// 若沒有調用方法setDispatchQueue,客戶端將爲連接新建一個隊列。如果想實現多個連接使用公用的隊列,顯式地指定隊列是一個非常方便的實現方法
mqtt.setDispatchQueue(Dispatch.createQueue("foo"));
上面都是一些配置的問題,具體情況自己決定配置。具體的配置也可以參考下面的網址,這個網址也有詳細的描述:https://gitee.com/iots/mqtt-client。
下面開始講講連接和訂閱和發送主題
fusesource提供三種mqtt client api,分別爲阻塞API,基於Futur的API和回調API。
其中,阻塞API是在MQTT.connectBlocking方法建立連接和提供阻斷API的連接。
基於Futur的API則是:在MQTT.connectFuture方法建立連接,爲您提供了一個與結合Futur的連接。所有操作的連接是無阻塞的,並且經由返回的結果。
回調API是最複雜的也是性能最好的,另外兩種均是對回調API的封裝。
因爲回調API有些複雜,現在只是介紹回調API的封裝。就是前兩個,前兩個的區別是第一個爲阻塞的,第二個不是阻塞。下面開始代碼演示。
第一個阻塞API。代碼如下:
// 接收消息
Message message = connection.receive();
// 將收到的消息封裝爲消息類
ReceiveRealValiedateVO realValiedateVO = JSON.parseObject(message.getPayloadBuffer().toByteArray(),
ReceiveRealValiedateVO.class);
System.out.println(realValiedateVO.getG_id());
if (realValiedateVO.getS_id().equals("1") && realValiedateVO.getS_id().equals("1")) {
// 新建json
JSONObject jsonParam = new JSONObject();
// 封裝json
jsonParam.put("s_id", realValiedateVO.getS_id());
jsonParam.put("g_id", realValiedateVO.getG_id());
jsonParam.put("seq", realValiedateVO.getSeq());
jsonParam.put("type", realValiedateVO.getType());
jsonParam.put("valid", "true");
String s = jsonParam.toString();
// 發送特定主題的消息
connection.publish("主題", s.getBytes(), QoS.AT_LEAST_ONCE, false);
}
// 打印主題
System.out.println(message.getTopic());
// byte[] payload = message.getPayload();
System.out.println(String.valueOf(message.getPayloadBuffer()));
// process the message then:
message.ack();
// 連接斷開
// connection.disconnect();
具體的解釋都在代碼裏了,剩下就沒有什麼了。
要注意的點就是連接中斷的處理,和對於服務器的處理。
第二種就是使用future連接,代碼如下:
// 使用future連接
FutureConnection connection = mqtt.futureConnection();
Future<Void> f1 = connection.connect();
f1.await();
// 訂閱消息
Future<byte[]> f2 = connection.subscribe(new Topic[] { new Topic("主題", QoS.AT_LEAST_ONCE) });
//
byte[] qoses = f2.await();
// 發送身份驗證消息.
// Future<Void> f3 = connection.publish("foo", "Hello".getBytes(),
// QoS.AT_LEAST_ONCE, false);
// 接收訂閱消息..
Future<Message> receive = connection.receive();
// 打印消息.
Message message = receive.await();
System.out.println(String.valueOf(message.getPayloadBuffer()));
// 迴應
message.ack();
//
Future<Void> f4 = connection.disconnect();
f4.await();
第三個是最難的,我這邊的代碼也是有點亂,直接上代碼吧。
// 監聽
connection.listener(new Listener() {
@Override
public void onPublish(UTF8Buffer topicmsg, Buffer msg, Runnable ack) {
// utf-8 is used for dealing with the garbled
String topic = topicmsg.utf8().toString();
String payload = msg.utf8().toString();
System.out.println(topic + " " + payload);
String Amsg = AuthenticationSendDemo.Authentication(topic, payload);
if (topic.equals("主題")) {
// 重起一個阻塞線程
connection.getDispatchQueue().execute(new Runnable() {
public void run() {
connection.publish("主題", Amsg.getBytes(), QoS.AT_LEAST_ONCE, false,
new Callback<Void>() {
@Override
public void onSuccess(Void args) {
// 表示發佈主題成功
System.out.println("發佈成功!");
System.out.println("發佈的消息" + Amsg);
}
@Override
public void onFailure(Throwable throwable) {
// 表示發佈主題失敗
System.out.println("發佈失敗!");
}
});
}
});
}
// 表示監聽成功
ack.run();
}
@Override
public void onFailure(Throwable value) {
// 表示監聽失敗
}
// execute only once when connection is ended
@Override
public void onDisconnected() {
// 表示監聽到斷開連接
System.out.println("斷開連接!!");
}
// execute only once when connecting started
@Override
public void onConnected() {
// 表示監聽到連接成功
System.out.println("haha");
System.out.println();
}
});
因爲代碼中使用到了線程和回調,我對於這兩個掌握的也不是很好,也不再這裏亂扯,有大佬知道比較好的寫法最好指點一下。在這裏感謝。
三種寫法都寫完了,下面談一談感想和中間遇到的問題。
以爲看具體的文檔實在太多了,現在公司還在忙着趕項目,我這邊時間也不是很多,代碼的整理以後有時間在說。我感覺最重要的還是對於協議的一些掌握和體會,這些要比上面的代碼重要的多,因爲你最終的代碼還是要和項目整合的,和Spring整合的時候你會發現這些都是框架提供好了,你需要做的就是填參數,但是整合中遇到的問題的解決辦法都是你從寫上面的代碼中得到的。
因爲剛開始寫代碼,所以代碼中的註釋也是非常多的,這裏也不再累述。寫上面的代碼的時候遇到了很多的問題,解決的網站都在我第一篇MQTT博客中,比如MQTT的官網,網上的文章都是抄的,要不就是一知半解(我也是)。最終還是看自己的深入體會。
就這樣吧,結束。