寫在前面
最近有需求要了解一下各個推送的協(xié)議,目前了解到實現(xiàn)推送的三個主要方式:MQTT、XMPP和Google Cloud Message(GCM)。第三種方式暫不研究,前兩種都要看一看,本篇討論一下MQTT協(xié)議吧。本文使用阿里云Ubuntu云服務(wù)器安裝代理服務(wù)器,使用eclipse paho實現(xiàn)的MqttClient編寫代碼。文中的所使用的賬戶名和密碼在本文發(fā)布后將會更改,請各位自行搭建環(huán)境。本文包括以下內(nèi)容:
- MQTT簡介
- MQTT優(yōu)勢
- MQTT開發(fā)環(huán)境搭建
- 使用PAHO實現(xiàn)MQTT推送
MQTT簡介 & MQTT優(yōu)勢
MQTT全稱是Message Queuing Telemetry Transport,MQTT是IBM開發(fā)的基于TCP/IP協(xié)議的輕量級通訊協(xié)議。MQTT是一個客戶端服務(wù)端架構(gòu)的發(fā)布-訂閱(publish-subscribe)的消息傳輸協(xié)議。它的設(shè)計思想是開放、簡單、輕量、易于實現(xiàn)。這些特點使它適用于受限環(huán)境。例如,但不僅限于:
- 網(wǎng)絡(luò)代價昂貴,帶寬低、不可靠
- 在嵌入式設(shè)備中運行,處理器和內(nèi)存資源有限
作為一個物聯(lián)網(wǎng)專業(yè)的畢業(yè)生,看了以上的描述已經(jīng)心動了,很適合作為傳感器節(jié)點之間的通訊協(xié)議哇!哦,忘了,我現(xiàn)在是個Androider……MQTT控制報文頭部僅有2字節(jié)的長度,降低了網(wǎng)絡(luò)傳輸所需要的流量。MQTT支持三種不同級別的服務(wù)質(zhì)量(Quality of Service,QoS)為不同場景提供消息可靠性:
- 級別0:盡力而為。消息發(fā)送者會想盡辦法發(fā)送消息,但是遇到意外并不會重試。
- 級別1:至少一次。消息接受者如果沒有知會或者知會本身丟失,消息發(fā)送者會在此發(fā)送以保證消息接收者至少會收到一次,當(dāng)然可能造成重復(fù)消息。
- 級別2:恰好一次。保證這種語義肯定會減少并發(fā)或者增加延時,不過丟失或者重復(fù)消息是不可接受的時候,級別2是最合適的。
如果各位讀完了這些仍然覺得不過癮,沒有戳中各位的痛點,可以去讀一下MQTT的協(xié)議規(guī)范,這里中英文版本都有挑自己愛看的讀一下就好。
MQTT開發(fā)環(huán)境搭建
首先需要一個代理服務(wù)器,這里mqtt代理服務(wù)器使用的是apache的apollo,apollo支持STOMP,AMQP,MQTT,Openwire,SSL和WebSockets。下載戳這。

下載到本地之后,將之上傳到服務(wù)器上:
$ scp 文件名 $username@$ip:~
解壓tar.gz:
$ tar zxvf apache-apollo-1.7.1-unix-distro.tar.gz
進入解壓后的bin目錄下執(zhí)行apollo create testbroker命令創(chuàng)建一個名稱為testbroker的代理服務(wù)器。
$ cd apache-apollo-1.7.1-unix-distro.tar.gz/bin/
$ ./apollo create testbroker
輸入ls命令就可以看到文件夾下多了一個testbroker的文件夾

進入testbroker的bin文件夾下,執(zhí)行apollo-broker run 啟動代理服務(wù)器。進入testbroker文件下的etc文件夾,可以看到名為users.properties的文件,可以看到在最后配置了用戶名和密碼:

該文件夾下的apollo.xml中配置了端口和ip,不過這里就不管了。代理服務(wù)器配置完畢,接下來就是下載paho實現(xiàn)的mqtt client的jar包了。
下載地址

使用PAHO實現(xiàn)MQTT推送
這里利用Idea編寫Java程序?qū)崿F(xiàn),對于Android程序來說只需要稍加修改就可直接使用。首先新建一個Java項目,接著將上面下載的jar包作為依賴導(dǎo)入。首先編寫服務(wù)端:
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
public class MqttServer {
/**
* 代理服務(wù)器ip地址
*/
public static final String MQTT_BROKER_HOST = "tcp://xiasuhuei321.com:61613";
/**
* 訂閱標(biāo)識
*/
public static final String MQTT_TOPIC = "test";
private static String userName = "admin";
private static String password = "password";
/**
* 客戶端唯一標(biāo)識
*/
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("已經(jīng)發(fā)送");
Thread.sleep(10000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
這里的邏輯非常簡單,創(chuàng)建一個MqttClient,每十秒發(fā)送一次消息,訂閱了相應(yīng)topic的客戶端將會收到這個消息。接下來編寫客戶端:
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
public class MyMqttClient {
/**
* 代理服務(wù)器ip地址
*/
public static final String MQTT_BROKER_HOST = "tcp://xiasuhuei321.com:61613";
/**
* 客戶端唯一標(biāo)識
*/
public static final String MQTT_CLIENT_ID = "android_xiasuhuei321";
/**
* 訂閱標(biāo)識
*/
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,一般以客戶端唯一標(biāo)識符表示,
// MemoryPersistence設(shè)置clientid的保存形式,默認為以內(nèi)存保存
// 設(shè)備id不要太騷氣!!?。。。?!
mqttClient = new MqttClient(MQTT_BROKER_HOST, MQTT_CLIENT_ID, new MemoryPersistence());
// 配置參數(shù)信息
options = new MqttConnectOptions();
// 設(shè)置是否清空session,這里如果設(shè)置為false表示服務(wù)器會保留客戶端的連接記錄,
// 這里設(shè)置為true表示每次連接到服務(wù)器都以新的身份連接
options.setCleanSession(true);
// 設(shè)置用戶名
options.setUserName(USERNAME);
// 設(shè)置密碼
options.setPassword(PASSWORD.toCharArray());
// 設(shè)置超時時間 單位為秒
options.setConnectionTimeout(10);
// 設(shè)置會話心跳時間 單位為秒 服務(wù)器會每隔1.5*20秒的時間向客戶端發(fā)送個消息判斷客戶端是否在線,但這個方法并沒有重連的機制
options.setKeepAliveInterval(20);
// 連接
mqttClient.connect(options);
// 訂閱
mqttClient.subscribe("test");
// 設(shè)置回調(diào)
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();
}
}
}
接下來啟動服務(wù)端和客戶端看一下效果


思考
到這也差不多了,說實話,在Android中難的從來都不是實現(xiàn)推送,而是如何保證接收推送的服務(wù)存活。在Android對后臺服務(wù)限制越來越大的現(xiàn)在,自己實現(xiàn)推送的意義可能并不是非常大。但是對于一些特殊的應(yīng)用場景下,比如用戶打開應(yīng)用進行的一些操作需要用到長連接,自己實現(xiàn)推送可能會更加可靠一些(聽朋友說三方推送有時會莫名其妙收不到推送)。