參考資料:http://blog.csdn.net/qq_17250009/article/details/52774472html
MQTT官網:http://mqtt.org/java
MQTT介紹:http://www.ibm.comandroid
MQTT Android github:https://github.com/eclipse/paho.mqtt.androidgit
MQTT API:http://www.eclipse.org/paho/files/javadoc/index.html github
MQTT Android API: http://www.eclipse.org/paho/files/android-javadoc/index.htmlapache
前言windows
這幾天因一些需求要搞個雲端通訊,原本想是直接找個比較完善的物聯網平臺調用他們的SDK就好了瀏覽器
然而顯然不可能這麼順利,直接下阿里雲和騰訊雲的物聯網平臺Android鏈接例程,都不能直接用。。。安全
而我目前只懂一點點Android,關於聯網部分是徹底空白。。。因此想改也改不來。服務器
不過我發現這些物聯網雲平臺基本都用了MQTT協議,那既然直接上啃不動,咱們就慢慢來,哪裏的基礎知識不會就去補哪裏。
所以我決定先來了解MQTT協議,實現MQTT協議的通訊。
網上關於這個的教程很多,可是我的碰到的問題均可能不一樣,不必定都會在網上提到,特此把這幾天作的仔細記錄下來,說不定能幫到和我同樣要搞這方面的小白。
首先是安裝服務器,版本挺多的,http://activemq.apache.org/apollo/download.html,這是我下載的Apollo
我目前只安了windows的,因此也只說這個安裝,之後有機會補充Linux安裝
windows-MQTT服務器安裝
一、去上面的網站下載解壓。
二、命令行進入解壓文件夾下bin
三、運行apollo create mybroker命令,其中mybroker是服務器的名字,能夠本身改,此時bin下應該有個mybroker文件夾了
四、根據提示運行服務器便可,我這邊就是輸入命令:"D:\Download\apache-apollo-1.7.1-windows-distro\apache-apollo-1.7.1\bin\mybroker\bin\apollo-broker" run
五、啓動成功,顯示以下
圖1
注意最後七八行的內容,分別寫出了相應協議訪問服務器的端口(後面會用到),最後兩行寫了服務器管理頁面的地址,在瀏覽器裏輸入地址便可查看服務器的一些信息(網頁須要輸入帳戶密碼登陸,默認的話就是admin,password)
注:安裝目錄\etc\apollo.xml文件下是配置服務器信息的文件。etc\users.properties文件包含鏈接MQTT服務器時用到的用戶名和密碼,默認爲admin=password,即帳號爲admin,密碼爲password,可自行更改。
至此 windows端安裝mqtt服務器就已經完成了,這一步比較容易,網上相關教程不少,關鍵咱們還得實際創建客戶端通訊如下,看到效果才知道服務器真的有用。下面纔是關鍵。
Android客戶端MQTT通訊Demo
首先咱們須要找到MQTT通訊的java包,導入Android Studio
下載連接:https://pan.baidu.com/s/1nw39tYp
jar導入Android Studio教程參考:http://www.cnblogs.com/otaganyuki/p/8495125.html
接下來就能夠調用此包與服務器創建鏈接,嘗試發送消息了
創建鏈接主要用到MqttClient這個類,須要設置的參數能夠具體看下面代碼,這裏先特別說如下傳入的localhost,
我原先並無什麼網絡方面的知識,只是知道這個要傳個ip地址,便百度搜ip地址,百度就會告訴你你的ip地址
我就把它填上去了,然而怎麼也連不上。。。後來我想會不會是要從本身電腦裏「網絡和internet設置」那裏 查看本身的ip
結果一看,這兩個ip確實不同,填後者在模擬器上就能夠鏈接成功,這裏我基本就有點頭緒了。
ip地址分爲內網ip和外網ip,外網ip是咱們接入因特網的地址,可是這個地址不必定就是一臺設備獨有的,外網ip會分給某些服務器和局域網
在局域網下,全部連着局域網的計算機都共有一個外網ip,經過從某個共同的出口訪問因特網。
而內網ip是在一個局域網下用於區分局域網下的計算機所設置的地址。
個人電腦wifi連在路由器做爲服務器,開的虛擬機也是經過這裏訪問網絡,我填的內網ip它就能訪問到個人電腦的服務器。
可是填的外網ip地址就不行,這是爲何呢
我猜想多是由於外網ip只能定位到我所鏈接的局域網的ip地址,沒法定位到局域網下的某一臺機器,天然就無法訪問個人服務器了
可是若是隻能在局域網下通訊就未免有點沒意思了,經過因特網絡鏈接方法有如下幾種
一、租個有獨立ip的服務器,經過其ip訪問
二、內網穿透/端口映射
因爲這些我當時只是經過猜想和百度得出的推測,因此我想先儘快試試可不能夠鏈接驗證推測,而不是花錢買服務器這麼麻煩的事。
我試了內網穿透,確實是能夠的。內網穿透具體百度,大概就是將本計算機某個端口和某個具備獨立ip的服務器的某個端口進行映射,
這樣客戶端訪問具備獨立ip的服務器,獨立ip的服務器又把請求發到本身的電腦上,實現了鏈接。
提供內網穿透服務的東西蠻多的,我用的是花生殼(花了六塊錢一個月,最省力,其它ngrok之類的比較麻煩)
圖2
稍微說一下那個花生殼吧,安裝後點內網穿透,在彈出的網頁裏 點擊添加映射
填的ip是本身做服務器的計算機的內網ip,端口隨機吧,固定的要錢。以後就會出現如上內容(我第一天搞得時候上面的東西出不來,次日就行了。。。。)
以後就根據前面的網址 查到其外網ip地址,用這個ip填入客戶端鏈接,端口也要用圖2裏的端口(若是是內網裏面鏈接就填圖1裏的後兩行上的端口)
這個解決了就容易了,如下爲MainActivity代碼,複製進去再在xml文件里加兩個按鈕佈局,別忘了在AndroidManifest里加上權限(加在package下面)
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
就能運行了,若是沒加權限,會很快彈出fail 異常內容
package com.example.a86275.mqtt_test; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.Toast; import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; import org.eclipse.paho.client.mqttv3.MqttCallback; import org.eclipse.paho.client.mqttv3.MqttClient; import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; import java.util.concurrent.ScheduledExecutorService; public class MainActivity extends AppCompatActivity { public String TAG="MyTAG"; //private String host = "tcp://內網ip:服務器端口號"; private String host = "tcp://外網代理ip:端口號";//代理ip 花生殼 private String userName = "admin"; private String passWord = "password"; private String clientId="AndroidClient1"; private int i = 1; private MqttClient client; private String myTopic = "AndroidTopic"; private MqttConnectOptions options; //private ScheduledExecutorService scheduler; public Handler handler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); if (msg.what == 1) { Toast.makeText(MainActivity.this,"Success",Toast.LENGTH_SHORT).show(); try { client.subscribe(myTopic, 1); } catch (Exception e) { e.printStackTrace(); } }else if(msg.what==2){ Toast.makeText(MainActivity.this,"fail",Toast.LENGTH_SHORT).show(); }else if(msg.what==3){ Toast.makeText(MainActivity.this,(String)msg.obj,Toast.LENGTH_SHORT).show(); Log.d(TAG, "handleMessage"); } } }; Button button1=(Button)findViewById(R.id.button1); Button button2=(Button)findViewById(R.id.button2); button1.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View v){ class MyThread extends Thread{ @Override public void run(){ init(); try { client.connect(options); Message msg = new Message(); msg.what = 1; handler.sendMessage(msg);//鏈接成功 } catch (Exception e) { e.printStackTrace(); Message msg = new Message(); msg.what = 2; handler.sendMessage(msg); //鏈接失敗 } } } new MyThread().start(); } }); button2.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View v){ publish("MessageReceived"); } }); } private void init() { try { //host爲主機名,test爲clientid即鏈接MQTT的客戶端ID,通常以客戶端惟一標識符表示,MemoryPersistence設置clientid的保存形式,默認爲之內存保存 client = new MqttClient(host, clientId, new MemoryPersistence()); //MQTT的鏈接設置 options = new MqttConnectOptions(); //設置是否清空session,這裏若是設置爲false表示服務器會保留客戶端的鏈接記錄,這裏設置爲true表示每次鏈接到服務器都以新的身份鏈接 options.setCleanSession(true); //設置鏈接的用戶名 options.setUserName(userName); //設置鏈接的密碼 options.setPassword(passWord.toCharArray()); // 設置超時時間 單位爲秒 options.setConnectionTimeout(10); // 設置會話心跳時間 單位爲秒 服務器會每隔1.5*20秒的時間向客戶端發送個消息判斷客戶端是否在線,但這個方法並無重連的機制 options.setKeepAliveInterval(20); //設置回調 client.setCallback(mqttCallback); } catch (Exception e) { e.printStackTrace(); } } private MqttCallback mqttCallback = new MqttCallback() { @Override public void messageArrived(String topic, MqttMessage message) throws Exception {//This method is called when a message arrives from the server String str1 = new String(message.getPayload()); Log.d(TAG, "messageArrived: "+str1); Message msg=new Message(); msg.what=3; msg.obj=str1; handler.sendMessage(msg); } @Override public void deliveryComplete(IMqttDeliveryToken arg0) { //Called when delivery for a message has been completed } @Override public void connectionLost(Throwable arg0) { // This method is called when the connection to the server is lost. Log.d(TAG, "connectionLost: "); } }; public void publish(String msg){ String topic = myTopic; Integer qos = 0; Boolean retained = false; try { client.publish(topic, msg.getBytes(), qos.intValue(), retained.booleanValue()); } catch (MqttException e) { e.printStackTrace(); } } @Override protected void onDestroy() { super.onDestroy(); try { scheduler.shutdown(); client.disconnect(); } catch (MqttException e) { e.printStackTrace(); } } }
佈局代碼
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="connect"
android:id="@+id/button1"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="send"
android:id="@+id/button2"/>
運行效果就是點擊connect,鏈接成功會彈出success(很快),同時能在管理服務器的網頁上看到鏈接的設備,不然會彈出fail(過好一下子才彈出),點擊send就會發送消息到某個topic 全部訂閱了此topic的客戶端都會接受到此消息
我在模擬器和手機都訂閱同一個topic,其中一個點send兩個都會彈出「message received」
效果以下
以上就實現了安卓客戶端和服務器的鏈接和通訊。後面會講樹莓派和服務器創建鏈接和通訊。
後來補充的,我本身碰到的一點問題
權限以前講過了,不過容易忘 ,再提一下
剛調用創建鏈接的線程後不能立刻調用pulish方法發送消息,此時鏈接並無建好,可能會出現空指針異常致使活動中止
主題並不須要建立,直接使用就是了。
主題多是管理員在服務端預先定義好的,也多是服務端收到第一個訂閱或使用那個主題名的應用消息時動態添加的。服務端也可使用一個安全組件有選擇地受權客戶端使用某個主題資源。