MQTT协议在物联网方面的应用
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是IBM开发的一个即时通讯协议,有可能成为物联网的重要组成部分。该协议支持所有平台,几乎可以把所有联网物品和外部连接起来,被用来当做传感器和制动器(比如通过Twitter让房屋联网)的通信协议。
物联网(Internet of Things,IoT)最近曝光率越来越高。虽然HTTP是网页的事实标准,不过机器之间(Machine-to-Machine,M2M)的大规模沟通需要不同的模式:之前的请求/回答(Request/Response)模式不再合适,取而代之的是发布/订阅(Publish/Subscribe)模式。这就是轻量级、可扩展的MQTT(Message Queuing Telemetry Transport)可以施展拳脚的舞台。
MQTT应用领域
MQTT是基于二进制消息的发布/订阅编程模式的消息协议,最早由IBM提出的,如今已经成为OASIS规范。由于规范很简单,非常适合需要低功耗和网络带宽有限的IoT场景,比如:
遥感数据
汽车
智能家居
智慧城市
医疗医护
由于物联网的环境是非常特别的,所以MQTT遵循以下设计原则:
精简,不添加可有可无的功能。
发布/订阅(Pub/Sub)模式,方便消息在传感器之间传递。
允许用户动态创建主题,零运维成本。
把传输量降到最低以提高传输效率。
把低带宽、高延迟、不稳定的网络等因素考虑在内。
支持连续的会话控制。
理解客户端计算能力可能很低。
提供服务质量管理。
假设数据不可知,不强求传输数据的类型与格式,保持灵活性
服务质量
为了满足不同的场景,MQTT支持三种不同级别的服务质量(Quality of Service,QoS)为不同场景提供消息可靠性:
级别0:尽力而为。消息发送者会想尽办法发送消息,但是遇到意外并不会重试。
级别1:至少一次。消息接收者如果没有知会或者知会本身丢失,消息发送者会再次发送以保证消息接收者至少会收到一次,当然可能造成重复消息。
级别2:恰好一次。保证这种语义肯待会减少并发或者增加延时,不过丢失或者重复消息是不可接受的时候,级别2是最合适的。
服务质量是个老话题了。级别2所提供的不重不丢很多情况下是最理想的,不过往返多次的确认一定对并发和延迟带来影响。级别1提供的至少一次语义在日志处理这种场景下是完全OK的,所以像Kafka这类的系统利用这一特点减少确认从而大大提高了并发。级别0适合鸡肋数据场景,食之无味弃之可惜,就这么着吧。
消息类型
MQTT拥有14种不同的消息类型:
CONNECT:客户端连接到MQTT代理
CONNACK:连接确认
PUBLISH:新发布消息
PUBACK:新发布消息确认,是QoS 1给PUBLISH消息的回复
PUBREC:QoS 2消息流的第一部分,表示消息发布已记录
PUBREL:QoS 2消息流的第二部分,表示消息发布已释放
PUBCOMP:QoS 2消息流的第三部分,表示消息发布完成
SUBSCRIBE:客户端订阅某个主题
SUBACK:对于SUBSCRIBE消息的确认
UNSUBSCRIBE:客户端终止订阅的消息
UNSUBACK:对于UNSUBSCRIBE消息的确认
PINGREQ:心跳
PINGRESP:确认心跳
DISCONNECT:客户端终止连接前优雅地通知MQTT代理
具体的协议详解可移步到
由于小编是android开发工程师,下面的例子就以android代码为例:
首先我们要搭建一个服务器
Apollo 安装配置
第一步下载安装
本地电脑Windows7 64位下载地址:http://activemq.apache.org/apollo/download.html下载Apollo服务器并解压,在CMD环境运行其工作目录下的 bin\apollo.cmd,命令后面带上参数「create mybroker」,创建服务器实例。这里需要Java环境,系统环境变量下要有JAVA_HOME。创建实例之后会在bin目录下生成mybroker文件夹,其中 etc\apollo.xml 文件下是配置服务器信息的文件,etc\users.properties 文件包含连接MQTT服务器时用到的用户名和密码,初始默认帐号是admin,密码password;注意这里要设置APOLLO_HOME启动服务:
在安装目录下执行: mybroker\bin\apollo-broker run
会出现如下界面:
其中我们要留意的:
MQTT服务器TCP连接端口:tcp://0.0.0.0:61613
后台登录接口:
https://127.0.0.1:61681/或
http://127.0.0.1:61680/
进入后台管理界面:
登录服务器后,如果MQTT服务器有客户端连接,后台会显示如下
下面就写服务端的代码:首先要明确的是服务端是相对的,一个设备即是服务端也可能是客户端!
这里应该说是。发布/订阅的关系,一个硬件设备既要能发送消息,也能接收消息,这样才能对硬件的数据进行监控和远程操控
下面是小编整理的android端的发送和接受代码:
首先需要添加依赖包:
compile files(‘libs/org.eclipse.paho.client.mqttv3-1.0.1.jar’)
compile ‘org.eclipse.paho:org.eclipse.paho.android.service:1.1.0’
发送消息端(发布)的代码
>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.MqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttTopic;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
/**
* Created by Administrator on 2017/11/8/008.
*/
public class Serv {
private static final long serialVersionUID = 1L;
private MqttClient client;
private String host = "tcp://192.168.1.5:61613";
// private String host = "tcp://localhost:1883";
private String userName = "admin";
private String passWord = "password";
private MqttTopic topic;
private MqttMessage message;
private String myTopic = "test/topic";
private String mes="123456";
public static Serv serv;
public static Serv getInstance(String clind){
if (serv==null){
serv=new Serv(clind);
}
return serv;
}
private Serv(String clindeId) {
try {
client = new MqttClient(host, clindeId,
new MemoryPersistence());
} catch (MqttException e) {
e.printStackTrace();
}
}
public void open(Context context){
if (client!=null){
connect();
Toast.makeText(context,"服务器打开",Toast.LENGTH_SHORT).show();
}
}
public boolean isOpen(){
boolean connected=false;
if (client!=null){
connected= client.isConnected();
}
return connected;
}
public void close(Context context){
if (client.isConnected()){
try {
client.disconnect();
serv=null;
Toast.makeText(context,"服务器关闭连接",Toast.LENGTH_SHORT).show();
} catch (MqttException e) {
e.printStackTrace();
}
}
}
public void setMessage(String s){
this.mes=s;
MqttDeliveryToken token = null;
try {
message = new MqttMessage();
message.setQos(1);
message.setRetained(true);
System.out.println(message.isRetained()+"------ratained状态");
message.setPayload(mes.getBytes());
token = topic.publish(message);
token.waitForCompletion();
System.out.println(token.isComplete()+"========");
} catch (MqttException e) {
e.printStackTrace();
}
}
private void connect() {
MqttConnectOptions options = new MqttConnectOptions();
options.setCleanSession(false);
options.setUserName(userName);
options.setPassword(passWord.toCharArray());
// 设置超时时间
options.setConnectionTimeout(10);
// 设置会话心跳时间
options.setKeepAliveInterval(20);
try {
client.setCallback(new MqttCallback() {
@Override
public void connectionLost(Throwable cause) {
System.out.println("connectionLost-----------");
}
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
System.out.println("deliveryComplete---------"+token.isComplete());
}
@Override
public void messageArrived(String topic, MqttMessage arg1)
throws Exception {
System.out.println("messageArrived----------");
}
});
topic = client.getTopic(myTopic);
client.connect(options);
} catch (Exception e) {
e.printStackTrace();
}
}}
2.订阅消息的代码`
public class ClindeActivity extends AppCompatActivity {
private TextView resultTv;
private String host = "tcp://192.168.1.5:61613";
private String userName = "admin";
private String passWord = "password";
private Handler handler;
private MqttClient client;
private String myTopic = "test/topic";
private MqttConnectOptions options;
private ScheduledExecutorService scheduler;
private Button viewById;
EditText post;
Button Link;
Serv serv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
resultTv = (TextView) findViewById(R.id.tv);
post= (EditText) findViewById(R.id.post);
viewById = (Button) findViewById(R.id.sendMessage);
Link= (Button) findViewById(R.id.Link);
Link.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
serv=Serv.getInstance(post.getText().toString().trim());
if (serv.isOpen()){
serv.close(getApplicationContext());
resultTv.setText("服务器停止");
}else {
serv.open(getApplicationContext());
resultTv.setText("服务器工作中");
}
}
});
final EditText et= (EditText) findViewById(R.id.edit);
this.viewById.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String s=et.getText().toString();
if (serv==null){
Toast.makeText(getApplicationContext(),"请先打开服务端",Toast.LENGTH_SHORT).show();
return;
}else {
serv.setMessage(s);
}
}
});
init();
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if(msg.what == 1) {
Toast.makeText(ClindeActivity.this, (String) msg.obj,
Toast.LENGTH_SHORT).show();
System.out.println("-----------------------------");
} else if(msg.what == 2) {
Toast.makeText(ClindeActivity.this, "连接成功", Toast.LENGTH_SHORT).show();
try {
client.subscribe(myTopic, 1);
} catch (Exception e) {
e.printStackTrace();
}
} else if(msg.what == 3) {
Toast.makeText(ClindeActivity.this, "连接失败,系统正在重连", Toast.LENGTH_SHORT).show();
}
}
};
startReconnect();
}
private void startReconnect() {
scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
if(!client.isConnected()) {
connect();
}
}
}, 0 * 1000, 10 * 1000, TimeUnit.MILLISECONDS);
}
private void init() {
try {
//host为主机名,test为clientid即连接MQTT的客户端ID,一般以客户端唯一标识符表示,MemoryPersistence设置clientid的保存形式,默认为以内存保存
client = new MqttClient(host, "123456",
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(new MqttCallback() {
@Override
public void connectionLost(Throwable cause) {
//连接丢失后,一般在这里面进行重连
System.out.println("connectionLost----------");
resultTv.setText("服务器来链接中断");
}
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
//publish后会执行到这里
System.out.println("deliveryComplete---------"
+ token.isComplete());
}
@Override
public void messageArrived(String topicName, MqttMessage message)
throws Exception {
//subscribe后得到的消息会执行到这里面
System.out.println("messageArrived----------");
Message msg = new Message();
msg.what = 1;
msg.obj = topicName+"---"+message.toString();
handler.sendMessage(msg);
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
private void connect() {
new Thread(new Runnable() {
@Override
public void run() {
try {
client.connect(options);
Message msg = new Message();
msg.what = 2;
handler.sendMessage(msg);
} catch (Exception e) {
e.printStackTrace();
Message msg = new Message();
msg.what = 3;
handler.sendMessage(msg);
}
}
}).start();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if(client != null && keyCode == KeyEvent.KEYCODE_BACK) {
try {
client.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
return super.onKeyDown(keyCode, event);
}
@Override
protected void onDestroy() {
super.onDestroy();
try {
scheduler.shutdown();
client.disconnect();
} catch (MqttException e) {
e.printStackTrace();
}
}`