物联网实战-基于开源 MQTT消息服务器EMQ X

物联网协议对比

对于物联网,最重要的是在互联网中设备与设备的通讯,现在物联网在internet通信中比较常见的通讯协议包括:HTTP、websocket、XMPP、COAP、MQTT

  1. HTTP和websocket 优劣势
    HTTP协议(HyperText Transfer Protocol,超文本传输协议)是因特网上应用最为广泛的一种网络传输协议,所有的WWW文件都必须遵守这个标准。
    适用范围:HTTP是一个基于TCP/IP通信协议来传递数据(HTML 文件, 图片文件, 查询结果等)。
    优势:

    1. 开发成本低,开放程度高
    2. 市场占有率高,几乎不需要学习成本
    3. 服务松耦合,基本能应用到所有场合

    劣势:

    1. 由于必须由设备主动向服务器发送数据,难以主动向设备推送数据
    2. 安全性不高
    3. 对于运算和存储资源都十分受限的设备,http协议实现、XML/JSON数据格式的解析,都是不可能的任务
    4. HTTP消息头占用数据量较大,耗费带宽资源,大大影响性能
  2. XMPP 优劣势
    XMPP(Extensible Messaging and Presence Protocol)可扩展通讯和表示协议,XMPP的前身是Jabber,一个开源形式组织产生的网络即时通信协议。XMPP目前被IETF国际标准组织完成了标准化工作。
    适用范围:即时通信的应用程序,还能用在网络管理、内容供稿、协同工具、档案共享、游戏、远端系统监控等。
    优势:

    1. 基于XML的协议,由于其开放性和易用性,在互联网及时通讯应用中运用广泛。
    2. 相对HTTP,XMPP在通讯的业务流程上是更适合物联网系统的,开发者不用花太多心思去解决设备通讯时的业务通讯流程,相对开发成本会更低

    劣势:

    1. 安全性不高
    2. 计算资源消耗,对于运算和存储资源都十分受限的设备不太友好
  3. COAP 优劣势
    CoAP (Constrained Application Protocol),受限应用协议,应用于无线传感网中协议。
    适用范围:CoAP是简化了HTTP协议的RESTful API,CoAP是6LowPAN协议栈中的应用层协议,它适用于在资源受限的通信的IP网络。
    优势:

    1. 采用UDP而不是TCP。这省去了TCP建立连接的成本及协议栈的开销。
    2. 将数据包头部都采用二进制压缩,减小数据量以适应低网络速率场景。
    3. 发送和接收数据可以异步进行,这样提升了设备响应速度。

    劣势:

    1. ipv6没有普及之前,适用于局域网内部(如wifi)通信
  4. MQTT 优劣势
    MQTT (Message Queuing Telemetry Transport ),消息队列遥测传输,由IBM开发的即时通讯协议,相比来说比较适合物联网场景的通讯协议。MQTT协议采用发布/订阅模式,所有的物联网终端都通过TCP连接到云端,云端通过主题的方式管理各个设备关注的通讯内容,负责将设备与设备之间消息的转发。
    适用范围:在低带宽、不可靠的网络下提供基于云平台的远程设备的数据传输和监控。
    优势:

    1. 所有的协议都是采用二进制格式编解码,并且编解码格式都非常易于开发和实现
    2. 最小的数据包只有2个字节,对于低功耗低速网络也有很好的适应性。
    3. 有非常完善的QOS机制,根据业务场景可以选择最多一次、至少一次、刚好一次三种消息送达模式。
    4. 运行在TCP协议之上,同时支持TLS(TCP+SSL)协议,并且由于所有数据通信都经过云端,安全性得到了较好地保障。

    劣势:

    1. 不适用设备与设备之间通信,因为所有的消息都要经过云端
    2. 实时性较差,一般都在秒级。

以上,就是各种协议的对比,其实还有很多其他的小众协议,这里就不一一阐述了。

对于小型设备或者单片机来说,首先HTPP和XMPP是可以排除掉的,设备解析传输数据都累的不行,还要去做业务。 那么就剩下MQTT和COAP协议,从技术方向来说,COAP是更适合物联网场景的,因为在传输效率上来说,COAP更加出色,但在IPV6未普及之前,这种协议仅仅是更适合局域网环境,可现在在云端化的趋势下,纯局域网的环境基本不可能了。所有最终选择了MQTT作为传输协议

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概述

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 消息服务器。

EMQ X R3.1 消息服务器功能列表

完整的 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 支持
规则引擎支持

EMQX 安装启动

下载地址: 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

物联网实战-基于开源 MQTT消息服务器EMQ X_第1张图片
EMQ X 消息服务器默认占用的 TCP 端口包括:
1883 MQTT 协议端口
8883 MQTT/SSL 端口
8083 MQTT/WebSocket 端口
8080 HTTP API 端口
18083 Dashboard 管理控制台端口

开源 MQTT 客户端项目

GitHub: https://github.com/emqtt

  1. emqttc Erlang MQTT 客户端库
  2. CocoaMQTT Swift 语言 MQTT 客户端库
  3. QMQTT QT 框架 MQTT 客户端库
  4. emqtt_benchmark MQTT 连接测试工具

推荐使用:Eclipse Paho: https://www.eclipse.org/paho/

MQTT.org: https://github.com/mqtt/mqtt.github.io/wiki/libraries

MQTT 概述

MQTT(MQ Telemetry Transport)是IBM开发的一种网络应用层的协议,提供轻量级的,支持可发布/可订阅的的消息推送模式,使设备对设备之间的短消息通信变得简单,比如现在应用广泛的低功耗传感器,手机、嵌入式计算机、微型控制器等移动设备。 它工作在 TCP/IP协议族上,是为硬件性能低下的远程设备以及网络状况糟糕的情况下而设计的发布/订阅型消息协议,为此,它需要一个消息中间件 。

MQTT 使用场景

  1. 不可靠、网络带宽小的网络
  2. 运行的设备CPU、内存非常有限

MQTT 特点

  1. 基于发布/订阅模型的协议
  2. 他是二进制协议,二进制的特点就是紧凑. 占用空间小。他的协议头只有2个字节
  3. MQTT协议是为大量计算能力有限,且工作在低带宽. 不可靠的网络的远程传感器和控制设备通讯而设计的协议,它具有以下主要的几项特性:
    a. 使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合;
    b. 对负载内容屏蔽的消息传输;
    c 使用 TCP/IP 提供网络连接;
    d. 有三种消息发布服务质量:
    “至多一次”,消息发布完全依赖底层 TCP/IP 网络。会发生消息丢失或重复。这一级别可用于如下情况,环境传感器数据,丢失一次读记录无所谓,因为不久后还会有第二次发送。
    “至少一次”,确保消息到达,但消息重复可能会发生。
    “只有一次”,确保消息到达一次。这一级别可用于如下情况,在计费系统中,消息重复或丢失会导致不正确的结果。
  4. 小型传输,开销很小(固定长度的头部是 2 字节),协议交换最小化,以降低网络流量;
  5. 使用 Last Will 和 Testament 特性通知有关各方客户端异常中断的机制

使用JAVA基于EMQX实现MQTT协议数据交互

实现功能:
1. 消息发布
2. 消息接收反馈
3. 服务端断线重连,恢复订阅

  1. 消息发布
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 {
		}
	}

}

  1. 接收并反馈信息
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 {
		}
	}

}

  1. 消息订阅并反馈
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
当然实际生产过程中,肯定会遇到很多问题,比如大规模设备请求下:

  1. 消费不及时,造成数据丢失、TCP管道破裂、EMQ X服务器内存崩溃,建议消费者采用生产者消费者模式开发
  2. 单一topic性能瓶颈,造成生产者发送数据失败、速度慢,建议参考动态DNS
  3. 网络抖动,造成TCP长链不稳定,建议参考观察者模式进行统一管理

你可能感兴趣的:(物联网实战-基于开源 MQTT消息服务器EMQ X)