简介
MQTT:全拼是Message Queuing Telemetry Transport,即:消息队列遥测传输协议;
MQTT是一种基于发布/订阅(publish/subscribe)模式的"轻量级"通讯协议,该协议构建于TCP/IP协议族上,由IBM在1999年发布。主要用于针对物联网应用中低宽带和网络环境不是很稳定的场景。比如智能硬件,车联网,智能家居,智慧城市,电力,能源等市场。
特点
1、使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合;
2、对负载内容屏蔽的消息传输;
3、使用 TCP/IP 提供网络连接;
4、有三种消息发布服务质量:
5、小型传输,开销很小(固定长度的头部是 2 字节),协议交换最小化,以降低网络流量;
6、使用 Last Will 和 Testament 特性通知有关各方客户端异常中断的机制
MQTT协议实现通信
实现MQTT协议需要客户端和服务器端通讯完成,在通讯过程中,MQTT协议中有三种身份:发布者(Publish)、代理(Broker)或者(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。
MQTT传输的消息分为:主题(Topic)和负载(payload)两部分:
a)Topic,可以理解为消息的类型,订阅者订阅(Subscribe)后,就会收到该主题的消息内容(payload);
b)payload,可以理解为消息的内容,是指订阅者具体要使用的内容。
官方文档
MQTT官网:http://mqtt.org/
MQTT介绍:http://www.ibm.com
Paho Android客户端页面:https://www.eclipse.org/paho/clients/android/
MQTT Android github:https://github.com/eclipse/paho.mqtt.android
MQTT API:http://www.eclipse.org/paho/files/javadoc/index.html
MQTT Android API: http://www.eclipse.org/paho/files/android-javadoc/index.html
Eclipse Paho
Eclipse Paho:是Eclipse提供的一个访问MQTT服务器的一种开源客户端库。类似的框架还有Xenqtt、 MeQanTT、 Fusesource mqtt -client、 moquette 等;目前主流是Paho;
Paho和MQTT协议的区别
Paho我的理解就是基于MQTT协议,封装的一个框架;就像Okhttp和http的区别一样;
类属性方法的解释
1,Topic:中文翻译是"话题"或者说"主题"。可以理解为消息的类型,订阅者订阅(Subscribe)后,就会收到该主题的消息内容(payload);在MQTT中订阅了(subscribe)同一话题(topic)的客户端会同时收到消息推送。类似“群聊”功能。
2,KeepAlive:"临终遗嘱"信息,该协议提供了检测方式,利用KeepAlive机制在客户端异常断开时发现问题。因此当客户端电量耗尽、崩溃或者网络断开时,消息代理会采取相应措施。
客户端会向任意点的消息代理发送“临终遗嘱”(LWT)信息,当消息代理检测到客户端离线(连接并未关闭),就会发送保存在特定主题上的 LWT 信息,让其它客户端知道该节点已经意外离线。
3,retained:要保留最后的断开连接信息。
4,Qos:服务质量。
qoS Level 0:至多一次
这是最简单的级别,无需客户端确认,其可靠性与基础网络层 TCP/IP 一致。
qoS Level 1:至少一次,有可能重复
确保至少向客户端发送一次信息,不过也可发送多次;在接收数据包时,需要客户端返回确认消息(ACK 包)。这种方式常用于传递确保交付的信息,但开发人员必须确保其系统可以处理重复的数据包。
qoS Level 2:只有一次,确保消息只到达一次
这是最不常见的服务质量级别,确保消息发送且仅发送一次。这种方法需要交换4个数据包,同时也会降低消息代理的性能。由于相对比较复杂,在 MQTT 实现中通常会忽略这个级别,请确保在选择数据库或消息代理前检查这个问题。Connect:等待与服务器建立连接。
5,MqttAndroidClient#subscribe():订阅某个话题。
6,MqttAndroidClient#publish():向某个话题发送消息,之后服务器会推送给所有订阅了此话题的客户。也可以向自己发送消息;
7,clientId:客户身份唯一标识。
8,userName:连接到MQTT服务器的用户名。
9,passWord :连接到MQTT服务器的密码。
10,UnSubscribe:等待服务器取消客户端的一个或多个topics订阅。
11,Disconnect:等待MQTT客户端完成所做的工作,并与服务器断开TCP/IP会话。
Android客户端
以下Android客户端是基于Eclipse Paho客户端开源库实现;
1,添加权限
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
2,项目下的bulid.gradle配置
buildscript {
repositories {
//....
maven {
url "https://repo.eclipse.org/content/repositories/paho-releases/"
}
//.....
}
}
3,添加Paho依赖
在module目录下的build.gradle中添加:
implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.1'
implementation 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1'
4,清单文件AndroidManifest.xml中注册Paho中的服务
<service android:name="org.eclipse.paho.android.service.MqttService" />
以上几步是使用Paho客户端前的基本配置;以下就是Paho 的Android客户端的具体应用了;既然是推送,一般是在后台运行,所以使用service创建MQTT客户端比较合适;
5,自定义Service,名字为MQTTService
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import org.eclipse.paho.android.service.MqttAndroidClient;
import org.eclipse.paho.client.mqttv3.IMqttActionListener;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.IMqttToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
public class MQTTService extends Service {
public static final String TAG = MQTTService.class.getSimpleName();
public static final String SN = "device_sn";
private static MqttAndroidClient client;
private MqttConnectOptions conOpt;
private String host = "自己服务器的地址";
private String userName = "用户名";
private String passWord = "密码";
private static String mTopic = "1020304050"; //要订阅的主题
private String clientId = mTopic + "b"; //客户端标识
private IGetMessageCallBack iGetMessageCallBack;
private static final Integer qos = 2;
@Override
public void onCreate() {
super.onCreate();
Log.e(getClass().getName(), "onCreate");
init();
}
public static void publish(String msg) {
String topic = mTopic;
Boolean retained = false;
try {
if (client != null) {
client.publish(topic, msg.getBytes(), qos.intValue(), retained.booleanValue());
}
} catch (MqttException e) {
e.printStackTrace();
}
}
private void init() {
String sn = (String) SPUtils.get(SN, "");
if (sn != null && !sn.isEmpty()) {
startConnect(sn);
} else {
if (Device.getInstance().getSn() == null) {
//TODO topic写死
startConnect("kanisa_001");
SPUtils.put(SN, "kanisa_001");
}else {
startConnect(Device.getInstance().getSn());
SPUtils.put(SN, Device.getInstance().getSn());
}
}
}
private void startConnect(String sn) {
Log.d(TAG, "设备号:" + sn);
mTopic = sn;
// 服务器地址(协议+地址+端口号)
String uri = host;
client = new MqttAndroidClient(KApp.getInstance().getApplicationContext(), uri, clientId);
// 设置MQTT监听并且接受消息
client.setCallback(mqttCallback);
conOpt = new MqttConnectOptions();
// 清除缓存
conOpt.setCleanSession(true);
// 设置超时时间,单位:秒
conOpt.setConnectionTimeout(10);
// 心跳包发送间隔,单位:秒
conOpt.setKeepAliveInterval(20);
// 用户名
conOpt.setUserName(userName);
// 密码
conOpt.setPassword(passWord.toCharArray()); //将字符串转换为字符串数组
//设置断开后重新连接
conOpt.setAutomaticReconnect(true);
// last will message
boolean doConnect = true;
String message = "{\"terminal_uid\":\"" + clientId + "\"}";
Log.e(getClass().getName(), "message是:" + message + " myTopic " + mTopic);
String topic = mTopic;
Boolean retained = false;
if ((!message.equals("")) || (!topic.equals(""))) {
// 最后的遗嘱
// MQTT本身就是为信号不稳定的网络设计的,所以难免一些客户端会无故的和Broker断开连接。
//当客户端连接到Broker时,可以指定LWT,Broker会定期检测客户端是否有异常。
//当客户端异常掉线时,Broker就往连接时指定的topic里推送当时指定的LWT消息。
try {
conOpt.setWill(topic, message.getBytes(), qos.intValue(), retained.booleanValue());
} catch (Exception e) {
Log.i(TAG, "Exception Occured", e);
doConnect = false;
iMqttActionListener.onFailure(null, e);
}
}
if (doConnect) {
doClientConnection();
}
}
@Override
public boolean onUnbind(Intent intent) {
client.unregisterResources();
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
stopSelf();
try {
if (client != null)
client.disconnect();
} catch (MqttException e) {
e.printStackTrace();
}
super.onDestroy();
}
/**
* 连接MQTT服务器
*/
private void doClientConnection() {
if (!client.isConnected() && isConnectIsNormal()) {
try {
client.connect(conOpt, null, iMqttActionListener);
} catch (MqttException e) {
e.printStackTrace();
}
}
}
// MQTT是否连接成功
private IMqttActionListener iMqttActionListener = new IMqttActionListener() {
@Override
public void onSuccess(IMqttToken arg0) {
Log.i(TAG, "连接成功 ");
try {
// 订阅myTopic话题
client.subscribe(mTopic, 1);
} catch (MqttException e) {
e.printStackTrace();
}
}
@Override
public void onFailure(IMqttToken arg0, Throwable arg1) {
arg1.printStackTrace();
// 连接失败,重连
Log.d(TAG, "连接失败");
}
};
// MQTT监听并且接受消息
private MqttCallback mqttCallback = new MqttCallback() {
@Override
public void messageArrived(String topic, MqttMessage message) {
String str1 = new String(message.getPayload());
if (iGetMessageCallBack != null) {
iGetMessageCallBack.setMessage(str1);
}
String str2 = topic + ";qos:" + message.getQos() + ";retained:" + message.isRetained();
Log.i(TAG, "messageArrived:" + str1);
Log.i(TAG, str2);
}
@Override
public void deliveryComplete(IMqttDeliveryToken arg0) {
}
@Override
public void connectionLost(Throwable arg0) {
// 失去连接,重连
}
};
/**
* 判断网络是否连接
*/
private boolean isConnectIsNormal() {
ConnectivityManager connectivityManager = (ConnectivityManager) this.getApplicationContext()
.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo info = connectivityManager.getActiveNetworkInfo();
if (info != null && info.isAvailable()) {
String name = info.getTypeName();
Log.i(TAG, "MQTT当前网络名称:" + name);
return true;
} else {
Log.i(TAG, "MQTT 没有可用网络");
return false;
}
}
@Override
public IBinder onBind(Intent intent) {
Log.e(getClass().getName(), "onBind");
return new CustomBinder();
}
public class CustomBinder extends Binder {
public MQTTService getService() {
return MQTTService.this;
}
}
public void setIGetMessageCallBack(IGetMessageCallBack iGetMessageCallBack) {
this.iGetMessageCallBack = iGetMessageCallBack;
}
}
IGetMessageCallBack 接口
public interface IGetMessageCallBack {
void setMessage(String message);
}
MqttServiceConnection类
import android.content.ComponentName;
import android.content.ServiceConnection;
import android.os.IBinder;
public class MqttServiceConnection implements ServiceConnection {
private MQTTService mqttService;
private IGetMessageCallBack iGetMessageCallBack;
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
mqttService = ((MQTTService.CustomBinder) iBinder).getService();
mqttService.setIGetMessageCallBack(iGetMessageCallBack);
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
public MQTTService getMqttService() {
return mqttService;
}
public void setIGetMessageCallBack(IGetMessageCallBack iGetMessageCallBack) {
this.iGetMessageCallBack = iGetMessageCallBack;
}
}
6,清单文件AndroidManifest.xml中配置服务
<service
android:name=".MQTTService"
android:enabled="true"
android:exported="true"/>
7,最后绑定服务即可;MQTT收到数据,通过注册回调的方式返回给Activity;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends AppCompatActivity implements IGetMessageCallBack {
private static final String TAG = MainActivity.class.getSimpleName();
private MqttServiceConnection serviceConnection;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main1);
initAndroidMQTT();
}
private void initAndroidMQTT() {
serviceConnection = new MqttServiceConnection();
serviceConnection.setIGetMessageCallBack(this);
//用Intent方式创建并启用Service
Intent intent = new Intent(this, MQTTService.class);
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
@Override
public void setMessage(String message) {
Log.d(TAG, "收到的推送数据:" + message);
}
}
8,收到推送数据,可以不用通过回调的方式返回,可以通过广播的方式把数据转发到需要的地方;