MQTT協議學習與在Java(Android通用)中的使用

xiasuhuei321發表於2017-12-13

寫在前面

最近有需求要了解一下各個推送的協議,目前瞭解到實現推送的三個主要方式:MQTT、XMPP和Google Cloud Message(GCM)。第三種方式暫不研究,前兩種都要看一看,本篇討論一下MQTT協議吧。本文使用阿里雲Ubuntu雲伺服器安裝代理伺服器,使用eclipse paho實現的MqttClient編寫程式碼。文中的所使用的賬戶名和密碼在本文釋出後將會更改,請各位自行搭建環境。本文包括以下內容:

  • MQTT簡介
  • MQTT優勢
  • MQTT開發環境搭建
  • 使用PAHO實現MQTT推送

MQTT簡介 & MQTT優勢

MQTT全稱是Message Queuing Telemetry Transport,MQTT是IBM開發的基於TCP/IP協議的輕量級通訊協議。MQTT是一個客戶端服務端架構的釋出-訂閱(publish-subscribe)的訊息傳輸協議。它的設計思想是開放、簡單、輕量、易於實現。這些特點使它適用於受限環境。例如,但不僅限於:

  • 網路代價昂貴,頻寬低、不可靠
  • 在嵌入式裝置中執行,處理器和記憶體資源有限

作為一個物聯網專業的畢業生,看了以上的描述已經心動了,很適合作為感測器節點之間的通訊協議哇!哦,忘了,我現在是個Androider……MQTT控制報文頭部僅有2位元組的長度,降低了網路傳輸所需要的流量。MQTT支援三種不同級別的服務質量(Quality of Service,QoS)為不同場景提供訊息可靠性:

  • 級別0:盡力而為。訊息傳送者會想盡辦法傳送訊息,但是遇到意外並不會重試。
  • 級別1:至少一次。訊息接受者如果沒有知會或者知會本身丟失,訊息傳送者會在此傳送以保證訊息接收者至少會收到一次,當然可能造成重複訊息。
  • 級別2:恰好一次。保證這種語義肯定會減少併發或者增加延時,不過丟失或者重複訊息是不可接受的時候,級別2是最合適的。

如果各位讀完了這些仍然覺得不過癮,沒有戳中各位的痛點,可以去讀一下MQTT的協議規範,這裡中英文版本都有挑自己愛看的讀一下就好。

MQTT開發環境搭建

首先需要一個代理伺服器,這裡mqtt代理伺服器使用的是apache的apollo,apollo支援STOMP,AMQP,MQTT,Openwire,SSL和WebSockets。下載戳這

點這個就行

下載到本地之後,將之上傳到伺服器上:

$ scp 檔名 $username@$ip:~
複製程式碼

解壓tar.gz:

$ tar zxvf apache-apollo-1.7.1-unix-distro.tar.gz
複製程式碼

進入解壓後的bin目錄下執行apollo create testbroker命令建立一個名稱為testbroker的代理伺服器。

$ cd apache-apollo-1.7.1-unix-distro.tar.gz/bin/
$ ./apollo create testbroker
複製程式碼

輸入ls命令就可以看到資料夾下多了一個testbroker的資料夾

testbroker
進入testbroker的bin資料夾下,執行apollo-broker run 啟動代理伺服器。進入testbroker檔案下的etc資料夾,可以看到名為users.properties的檔案,可以看到在最後配置了使用者名稱和密碼:

使用者名稱&密碼

該資料夾下的apollo.xml中配置了埠和ip,不過這裡就不管了。代理伺服器配置完畢,接下來就是下載paho實現的mqtt client的jar包了。 下載地址

下載這個

使用PAHO實現MQTT推送

這裡利用Idea編寫Java程式實現,對於Android程式來說只需要稍加修改就可直接使用。首先新建一個Java專案,接著將上面下載的jar包作為依賴匯入。首先編寫服務端:

import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;

public class MqttServer {
    /**
     * 代理伺服器ip地址
     */
    public static final String MQTT_BROKER_HOST = "tcp://xiasuhuei321.com:61613";

    /**
     * 訂閱標識
     */
    public static final String MQTT_TOPIC = "test";

    private static String userName = "admin";
    private static String password = "password";

    /**
     * 客戶端唯一標識
     */
    public static final String MQTT_CLIENT_ID = "android_server_xiasuhuei321";
    private static MqttTopic topic;
    private static MqttClient client;

    public static void main(String... args) {
        // 推送訊息
        MqttMessage message = new MqttMessage();
        try {
            client = new MqttClient(MQTT_BROKER_HOST, MQTT_CLIENT_ID, new MemoryPersistence());
            MqttConnectOptions options = new MqttConnectOptions();
            options.setCleanSession(true);
            options.setUserName(userName);
            options.setPassword(password.toCharArray());
            options.setConnectionTimeout(10);
            options.setKeepAliveInterval(20);

            topic = client.getTopic(MQTT_TOPIC);

            message.setQos(1);
            message.setRetained(false);
            message.setPayload("message from server".getBytes());
            client.connect(options);

            while (true) {
                MqttDeliveryToken token = topic.publish(message);
                token.waitForCompletion();
                System.out.println("已經傳送");
                Thread.sleep(10000);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

複製程式碼

這裡的邏輯非常簡單,建立一個MqttClient,每十秒傳送一次訊息,訂閱了相應topic的客戶端將會收到這個訊息。接下來編寫客戶端:

import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;

public class MyMqttClient {
    /**
     * 代理伺服器ip地址
     */
    public static final String MQTT_BROKER_HOST = "tcp://xiasuhuei321.com:61613";

    /**
     * 客戶端唯一標識
     */
    public static final String MQTT_CLIENT_ID = "android_xiasuhuei321";

    /**
     * 訂閱標識
     */
    public static final String MQTT_TOPIC = "xiasuhuei321";

    /**
     *
     */
    public static final String USERNAME = "admin";
    /**
     * 密碼
     */
    public static final String PASSWORD = "password";

    private volatile static MqttClient mqttClient;
    private static MqttConnectOptions options;

    public static void main(String... args) {
        try {
            // host為主機名,clientid即連線MQTT的客戶端ID,一般以客戶端唯一識別符號表示,
            // MemoryPersistence設定clientid的儲存形式,預設為以記憶體儲存
            // 裝置id不要太騷氣!!!!!!!
            mqttClient = new MqttClient(MQTT_BROKER_HOST, MQTT_CLIENT_ID, new MemoryPersistence());
            // 配置引數資訊
            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);
            // 連線
            mqttClient.connect(options);
            // 訂閱
            mqttClient.subscribe("test");
            // 設定回撥
            mqttClient.setCallback(new MqttCallback() {
                @Override
                public void connectionLost(Throwable throwable) {
                    System.out.println("connectionLost");
                }

                @Override
                public void messageArrived(String s, MqttMessage mqttMessage) throws Exception {
                    System.out.println("Topic: " + s + " Message: " + mqttMessage.toString());
                }

                @Override
                public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {

                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}
複製程式碼

接下來啟動服務端和客戶端看一下效果

服務端

客戶端

思考

到這也差不多了,說實話,在Android中難的從來都不是實現推送,而是如何保證接收推送的服務存活。在Android對後臺服務限制越來越大的現在,自己實現推送的意義可能並不是非常大。但是對於一些特殊的應用場景下,比如使用者開啟應用進行的一些操作需要用到長連線,自己實現推送可能會更加可靠一些(聽朋友說三方推送有時會莫名其妙收不到推送)。

相關文章