对于物联网,最重要的是在互联网中设备与设备的通讯,现在物联网在internet通信中比较常见的通讯协议包括:HTTP、websocket、XMPP、COAP、MQTT
HTTP和websocket 优劣势
HTTP协议(HyperText Transfer Protocol,超文本传输协议)是因特网上应用最为广泛的一种网络传输协议,所有的WWW文件都必须遵守这个标准。
适用范围:HTTP是一个基于TCP/IP通信协议来传递数据(HTML 文件, 图片文件, 查询结果等)。
优势:
劣势:
XMPP 优劣势
XMPP(Extensible Messaging and Presence Protocol)可扩展通讯和表示协议,XMPP的前身是Jabber,一个开源形式组织产生的网络即时通信协议。XMPP目前被IETF国际标准组织完成了标准化工作。
适用范围:即时通信的应用程序,还能用在网络管理、内容供稿、协同工具、档案共享、游戏、远端系统监控等。
优势:
劣势:
COAP 优劣势
CoAP (Constrained Application Protocol),受限应用协议,应用于无线传感网中协议。
适用范围:CoAP是简化了HTTP协议的RESTful API,CoAP是6LowPAN协议栈中的应用层协议,它适用于在资源受限的通信的IP网络。
优势:
劣势:
MQTT 优劣势
MQTT (Message Queuing Telemetry Transport ),消息队列遥测传输,由IBM开发的即时通讯协议,相比来说比较适合物联网场景的通讯协议。MQTT协议采用发布/订阅模式,所有的物联网终端都通过TCP连接到云端,云端通过主题的方式管理各个设备关注的通讯内容,负责将设备与设备之间消息的转发。
适用范围:在低带宽、不可靠的网络下提供基于云平台的远程设备的数据传输和监控。
优势:
劣势:
以上,就是各种协议的对比,其实还有很多其他的小众协议,这里就不一一阐述了。
对于小型设备或者单片机来说,首先HTPP和XMPP是可以排除掉的,设备解析传输数据都累的不行,还要去做业务。 那么就剩下MQTT和COAP协议,从技术方向来说,COAP是更适合物联网场景的,因为在传输效率上来说,COAP更加出色,但在IPV6未普及之前,这种协议仅仅是更适合局域网环境,可现在在云端化的趋势下,纯局域网的环境基本不可能了。所有最终选择了MQTT作为传输协议
参考这篇译文:https://blog.csdn.net/lordwish/article/details/85061687
说说我为什么会选择EMQ X这款服务软件作为消息代理服务器
对比过:Apache ActiveMQ、emqttd(后改名为EMQ X)、moquette
选择原因:
1.EMQ X是一款国产软件,在代码风格上来说,更贴切于国内的程序猿
2.EMQ X拥有非常健全的指导文档,丰富的社区
3.EMQ X拥有很直观的数据展示界面,这些是其他程序没有的
EMQ X (Erlang/Enterprise/Elastic MQTT Broker) 是基于 Erlang/OTP 语言平台开发,支持大规模连接和分布式集群,发布订阅模式的开源 MQTT 消息服务器。
EMQ X 消息服务器完整支持 MQTT V3.1/V3.1.1/V5.0 版本协议规范,并扩展支持 MQTT-SN 、WebSocket、CoAP、LwM2M、Stomp 以及私有 TCP/UDP 协议。EMQ X 消息服务器支持单节点100万连接与多节点分布式集群。
EMQ X 消息服务器为大规模设备连接 (C1000K+) 的物联网、车联网、智能硬件、移动推送、移动消息等应用,提供完全开放源码、安装部署简便、企业级稳定可靠、可弹性扩展、易于定制开发的 MQTT 消息服务器。
完整的 MQTT V3.1/V3.1.1 及V5.0协议规范支持
QoS0, QoS1, QoS2 消息支持
持久会话与离线消息支持
Retained 消息支持
Last Will 消息支持
TCP/SSL 连接支持
MQTT/WebSocket/SSL 支持
HTTP消息发布接口支持
KaTeX parse error: Expected 'EOF', got '#' at position 5: SYS/#̲ 系统主题支持 客户端在线状态…delay/topic)
Flapping 检测
黑名单支持
共享订阅($share//topic)
TLS/PSK 支持
规则引擎支持
下载地址: https://www.emqx.io/downloads/broker?osType=Linux
程序包下载后,可直接解压启动运行
unzip emqx-macosx-v3.1.0.zip && cd emqx
# 启动emqx
./bin/emqx start
# 检查运行状态
./bin/emqx_ctl status
# 停止emqx
./bin/emqx stop
EMQ X 启动后,MQTT 客户端可通过 1883 端口接入系统。运行日志输出在 log/ 目录。
EMQ X 默认加载 Dashboard 插件,启动 Web 管理控制台。用户可通过 Web 控制台,查看服务器运行状态、统计数据、连接(Connections)、会话(Sessions)、主题(Topics)、订阅(Subscriptions)、插件(Plugins)等。
控制台地址: http://127.0.0.1:18083,默认用户名: admin,密码:public
EMQ X 消息服务器默认占用的 TCP 端口包括:
1883 MQTT 协议端口
8883 MQTT/SSL 端口
8083 MQTT/WebSocket 端口
8080 HTTP API 端口
18083 Dashboard 管理控制台端口
GitHub: https://github.com/emqtt
推荐使用:Eclipse Paho: https://www.eclipse.org/paho/
MQTT.org: https://github.com/mqtt/mqtt.github.io/wiki/libraries
MQTT(MQ Telemetry Transport)是IBM开发的一种网络应用层的协议,提供轻量级的,支持可发布/可订阅的的消息推送模式,使设备对设备之间的短消息通信变得简单,比如现在应用广泛的低功耗传感器,手机、嵌入式计算机、微型控制器等移动设备。 它工作在 TCP/IP协议族上,是为硬件性能低下的远程设备以及网络状况糟糕的情况下而设计的发布/订阅型消息协议,为此,它需要一个消息中间件 。
实现功能:
1. 消息发布
2. 消息接收反馈
3. 服务端断线重连,恢复订阅
package com.nsac.mqtt.server;
import java.io.IOException;
import java.net.URISyntaxException;
import org.fusesource.mqtt.client.FutureConnection;
import org.fusesource.mqtt.client.MQTT;
import org.fusesource.mqtt.client.QoS;
/**
* 发布消息
*/
public class MQTTPub{
private String CONNECTION_STRING = "tcp://192.168.200.193:1883";
private boolean CLEAN_START = true;
private String CLIENT_ID = "server";
private short KEEP_ALIVE = 30;// 低耗网络,但是又需要及时获取数据,心跳30s
private long RECONNECTION_ATTEMPT_MAX = 6;
private long RECONNECTION_DELAY = 2000;
private int SEND_BUFFER_SIZE = 64;// 发送最大缓冲
private FutureConnection connection;
private String topic;
public static void main(String[] args) {
MQTTPub mqtt = new MQTTPub("aa-bb-cc-dd-ee");
mqtt.run();
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
public MQTTPub(String topic) {
this.CLIENT_ID = CLIENT_ID +"_"+ System.currentTimeMillis();
this.topic = topic;
}
/**
* 初始化MQTT服务,创建连接
*/
public void run() {
MQTT mqtt = new MQTT();
try {
// ==MQTT设置说明
// 设置服务端的ip
mqtt.setHost(CONNECTION_STRING);
// 连接前清空会话信息 ,若设为false,MQTT服务器将持久化客户端会话的主体订阅和ACK位置,默认为true
mqtt.setCleanSession(CLEAN_START);
// 设置心跳时间 ,定义客户端传来消息的最大时间间隔秒数,服务器可以据此判断与客户端的连接是否已经断开,从而避免TCP/IP超时的长时间等待
mqtt.setKeepAlive(KEEP_ALIVE);
// 设置客户端id,用于设置客户端会话的ID。在setCleanSession(false);被调用时,MQTT服务器利用该ID获得相应的会话。
// 此ID应少于23个字符,默认根据本机地址、端口和时间自动生成
mqtt.setClientId(CLIENT_ID);
mqtt.setUserName(CLIENT_ID);
// ==失败重连接设置说明
// 设置重新连接的次数 ,客户端已经连接到服务器,但因某种原因连接断开时的最大重试次数,超出该次数客户端将返回错误。-1意为无重试上限,默认为-1
mqtt.setReconnectAttemptsMax(RECONNECTION_ATTEMPT_MAX);
// 设置重连的间隔时间 ,首次重连接间隔毫秒数,默认为10ms
mqtt.setReconnectDelay(RECONNECTION_DELAY);
// 设置socket发送缓冲区大小,默认为65536(64k)
mqtt.setSendBufferSize(SEND_BUFFER_SIZE);
// //设置发送数据包头的流量类型或服务类型字段,默认为8,意为吞吐量最大化传输
mqtt.setTrafficClass(8);
mqtt.setVersion("3.1.1");
// ==带宽限制设置说明
mqtt.setMaxReadRate(0);// 设置连接的最大接收速率,单位为bytes/s。默认为0,即无限制
mqtt.setMaxWriteRate(0);// 设置连接的最大发送速率,单位为bytes/s。默认为0,即无限制
// 使用Future创建连接
connection = mqtt.futureConnection();
// 发布消息
connection.publish(topic, "aaaaaaaaaaaaaaaaaaa".getBytes(), QoS.AT_LEAST_ONCE, false);
} catch (URISyntaxException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
}
package com.nsac.mqtt.server;
import java.net.URISyntaxException;
import org.fusesource.hawtbuf.Buffer;
import org.fusesource.hawtbuf.UTF8Buffer;
import org.fusesource.mqtt.client.Callback;
import org.fusesource.mqtt.client.CallbackConnection;
import org.fusesource.mqtt.client.Listener;
import org.fusesource.mqtt.client.MQTT;
import org.fusesource.mqtt.client.QoS;
import org.fusesource.mqtt.client.Topic;
/**
* 服务端接收发布消息
*/
public class MQTTServer{
private String CONNECTION_STRING = "tcp://192.168.200.193:1883";
private boolean CLEAN_START = true;
private String CLIENT_ID = "server";
private short KEEP_ALIVE = 30;// 低耗网络,但是又需要及时获取数据,心跳30s
private long RECONNECTION_ATTEMPT_MAX = 6;
private long RECONNECTION_DELAY = 2000;
private int SEND_BUFFER_SIZE = 64;// 发送最大缓冲
private CallbackConnection connection;
private String topic;
public MQTTServer(String topic) {
this.CLIENT_ID = CLIENT_ID +"_"+ System.currentTimeMillis();
this.topic = topic;
}
/**
* 发布消息
*
* @param topic
* @param sendMsg
*/
public void publish(String topic, String sendMsg) {
connection.publish(topic, sendMsg.getBytes(), QoS.AT_LEAST_ONCE, false, new Callback<Void>() {
@Override
public void onSuccess(Void value) {
System.out.println("发送成功:" + topic);
}
@Override
public void onFailure(Throwable value) {
System.out.println("发送失败");
}
});
}
/**
* 订阅消息
*
* @param topic
*/
public void subscribe(String topic) {
connection.subscribe(new Topic[] { new Topic(topic, QoS.AT_LEAST_ONCE) },new Callback<byte[]>() {
@Override
public void onSuccess(byte[] bytes) {
System.out.println("订阅成功:" + topic);
}
@Override
public void onFailure(Throwable throwable) {
throwable.printStackTrace();
System.out.println("订阅失败:" + topic);
}
});
}
/**
* 取消订阅
*
* @param topic
*/
public void unsubscribe(String topic) {
connection.unsubscribe( new UTF8Buffer[] { UTF8Buffer.utf8(topic) } , new Callback<Void>() {
@Override
public void onSuccess(Void voids) {
System.out.println("退订成功");
connection.disconnect(new Callback<Void>() {
@Override
public void onSuccess(Void value) {
System.out.println("关闭成功");
}
@Override
public void onFailure(Throwable value) {
System.out.println("关闭失败");
}
});
}
@Override
public void onFailure(Throwable throwable) {
throwable.printStackTrace();
System.out.println("退订失败");
}
});
}
/**
* 初始化MQTT服务,创建连接
*/
public void run() {
MQTT mqtt = new MQTT();
try {
// ==MQTT设置说明
// 设置服务端的ip
mqtt.setHost(CONNECTION_STRING);
// 连接前清空会话信息 ,若设为false,MQTT服务器将持久化客户端会话的主体订阅和ACK位置,默认为true
mqtt.setCleanSession(CLEAN_START);
// 设置心跳时间 ,定义客户端传来消息的最大时间间隔秒数,服务器可以据此判断与客户端的连接是否已经断开,从而避免TCP/IP超时的长时间等待
mqtt.setKeepAlive(KEEP_ALIVE);
// 设置客户端id,用于设置客户端会话的ID。在setCleanSession(false);被调用时,MQTT服务器利用该ID获得相应的会话。
// 此ID应少于23个字符,默认根据本机地址、端口和时间自动生成
mqtt.setClientId(CLIENT_ID);
mqtt.setUserName(CLIENT_ID);
// ==失败重连接设置说明
// 设置重新连接的次数 ,客户端已经连接到服务器,但因某种原因连接断开时的最大重试次数,超出该次数客户端将返回错误。-1意为无重试上限,默认为-1
mqtt.setReconnectAttemptsMax(RECONNECTION_ATTEMPT_MAX);
// 设置重连的间隔时间 ,首次重连接间隔毫秒数,默认为10ms
mqtt.setReconnectDelay(RECONNECTION_DELAY);
// 设置socket发送缓冲区大小,默认为65536(64k)
mqtt.setSendBufferSize(SEND_BUFFER_SIZE);
// //设置发送数据包头的流量类型或服务类型字段,默认为8,意为吞吐量最大化传输
mqtt.setTrafficClass(8);
// ==带宽限制设置说明
mqtt.setMaxReadRate(0);// 设置连接的最大接收速率,单位为bytes/s。默认为0,即无限制
mqtt.setMaxWriteRate(0);// 设置连接的最大发送速率,单位为bytes/s。默认为0,即无限制
// 使用Future创建连接
connection = mqtt.callbackConnection();
connection.connect(new Callback<Void>() {
@Override
public void onSuccess(Void aVoid) {
//连接成功后会默认订阅主题($client/mengsu)
// System.out.println("连接成功");
subscribe(topic);
}
@Override
public void onFailure(Throwable throwable) {
}
});
connection.listener(new Listener() {
@Override
public void onConnected() {
}
@Override
public void onDisconnected() {
}
@Override
public void onPublish(UTF8Buffer utf8Buffer, Buffer buffer, Runnable ack) {
//当有设备向服务已订阅的主题发送消息时,该方法会消费
ack.run();
System.out.println("topic:" + utf8Buffer.toString());
System.out.println("收到消息:" + buffer.toString());
}
@Override
public void onFailure(Throwable throwable) {
}
});
// publish(topic, "ok");
} catch (URISyntaxException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
}
package com.nsac.mqtt.server;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.fusesource.hawtbuf.Buffer;
import org.fusesource.hawtbuf.UTF8Buffer;
import org.fusesource.mqtt.client.Callback;
import org.fusesource.mqtt.client.CallbackConnection;
import org.fusesource.mqtt.client.Listener;
import org.fusesource.mqtt.client.MQTT;
import org.fusesource.mqtt.client.QoS;
import org.fusesource.mqtt.client.Topic;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import com.alibaba.fastjson.JSONObject;
/**
* MQTT 连接监听器
*
* @author yangwei
*
*/
public class MqttConnectionListener {
private static JedisCluster jc;
private final static MQTT mqtt = new MQTT();
private final static String CONNECTION_STRING = "tcp://192.168.200.193:1883";
private final static boolean CLEAN_START = true;
private final static short KEEP_ALIVE = 30;// 低耗网络,但是又需要及时获取数据,心跳30s
private final static String CLIENT_ID = "ConnectionListener";
private final static Topic[] topics = { new Topic("$SYS/brokers/+/clients/#", QoS.AT_LEAST_ONCE) }; // 1 至少一次
private final static long RECONNECTION_ATTEMPT_MAX = 6;
private final static long RECONNECTION_DELAY = 2000;
private final static int SEND_BUFFER_SIZE = 64;// 发送最大缓冲为2M
public static Map<String, MQTTServer> map = new HashMap<String, MQTTServer>();
/**
* 用来连接服务器
*/
private void connect() {
try {
// 设置服务端的ip
mqtt.setUserName("ConnectionListener");
mqtt.setHost(CONNECTION_STRING);
// 连接前清空会话信息 ,若设为false,MQTT服务器将持久化客户端会话的主体订阅和ACK位置,默认为true
mqtt.setCleanSession(CLEAN_START);
// 设置心跳时间 ,定义客户端传来消息的最大时间间隔秒数,服务器可以据此判断与客户端的连接是否已经断开,从而避免TCP/IP超时的长时间等待
mqtt.setKeepAlive(KEEP_ALIVE);
// 设置客户端id,用于设置客户端会话的ID。在setCleanSession(false);被调用时,MQTT服务器利用该ID获得相应的会话。
// 此ID应少于23个字符,默认根据本机地址、端口和时间自动生成
mqtt.setClientId(CLIENT_ID);
// ==失败重连接设置说明
// 设置重新连接的次数 ,客户端已经连接到服务器,但因某种原因连接断开时的最大重试次数,超出该次数客户端将返回错误。-1意为无重试上限,默认为-1
mqtt.setReconnectAttemptsMax(RECONNECTION_ATTEMPT_MAX);
// 设置重连的间隔时间 ,首次重连接间隔毫秒数,默认为10ms
mqtt.setReconnectDelay(RECONNECTION_DELAY);
// 设置socket发送缓冲区大小,默认为65536(64k)
mqtt.setSendBufferSize(SEND_BUFFER_SIZE);
// //设置发送数据包头的流量类型或服务类型字段,默认为8,意为吞吐量最大化传输
mqtt.setTrafficClass(8);
// ==带宽限制设置说明
mqtt.setMaxReadRate(0);// 设置连接的最大接收速率,单位为bytes/s。默认为0,即无限制
mqtt.setMaxWriteRate(0);// 设置连接的最大发送速率,单位为bytes/s。默认为0,即无限制
// 获取mqtt的连接对象callbackConnection ,采用非阻塞模式 订阅主题
final CallbackConnection connection=mqtt.callbackConnection();
connection.connect(new Callback<Void>() {
@Override
public void onSuccess(Void aVoid) {
//连接成功后会默认订阅主题($client/mengsu)
System.out.println("连接成功");
//新建一个device主题
connection.subscribe(topics, new Callback<byte[]>() {
@Override
public void onSuccess(byte[] bytes) {
System.out.println("订阅成功" + "$SYS/brokers/+/clients/#");
}
@Override
public void onFailure(Throwable throwable) {
System.out.println("订阅失败" + "$SYS/brokers/+/clients/#");
}
});
}
@Override
public void onFailure(Throwable throwable) {
}
});
connection.listener(new Listener() {
@Override
public void onConnected() {
}
@Override
public void onDisconnected() {
}
@Override
public void onPublish(UTF8Buffer utf8Buffer, Buffer buffer, Runnable ack) {
//当有设备向服务已订阅的主题发送消息时,该方法会消费
String str = String.valueOf(buffer.toString()).replace("ascii: ", "");
JSONObject json = JSONObject.parseObject(str);
String clientId = json.getString("clientid").split("_")[0];
if (!clientId.contains("server")) {
// 需要暂停1秒再开始连接订阅
// 因为这里监听的是设备端连接到代理服务器,此时设备端很可能还没有订阅任何topic,只是上线,所以这里需要等1秒再向代理服务器发送数据
String[] data = utf8Buffer.toString().split("/");
MQTTServer mqttServer = map.get(clientId);
if ("connected".equals(data[data.length - 1 ])) {
System.out.println("老板,你的小可爱上线了!" + clientId);
if (mqttServer == null) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
mqttServer = new MQTTServer(clientId);
mqttServer.run();
map.put(clientId, mqttServer);
}
jc.lpush("subscribe_list", clientId);
} else if ("disconnected".equals(data[data.length -1]) && mqttServer != null) {
// System.out.println("断开连接:" + clientId);
jc.lrem("subscribe_list",0 , clientId);
map.remove(clientId);
mqttServer.unsubscribe(clientId);
}
}
ack.run();
}
@Override
public void onFailure(Throwable throwable) {
}
});
System.in.read();
} catch (Exception e) {
e.printStackTrace();
}
}
public void initReidsClientList() {
List<String> clients = jc.lrange("subscribe_list", 0, -1);
for (String clientId : clients) {
MQTTServer mqttServer = map.get(clientId);
if (mqttServer == null) {
mqttServer = new MQTTServer(clientId);
mqttServer.run();
map.put(clientId, mqttServer);
} else {
mqttServer.subscribe(clientId);
mqttServer.publish(clientId, "ok");
}
}
}
public static void main(String[] args) {
Set<HostAndPort> jedisClusterNodes = new HashSet<HostAndPort>();
jedisClusterNodes.add(new HostAndPort("192.168.200.193", 9101));
jc = new JedisCluster(jedisClusterNodes);
MqttConnectionListener sub = new MqttConnectionListener();
sub.initReidsClientList();
sub.connect();
}
}
注意:这里是将上线的客户端都记录到了redis里面,在启动的时候将这些客户端都订阅一遍
以上就是一个简单的消息发布和接受的demo
当然实际生产过程中,肯定会遇到很多问题,比如大规模设备请求下: