Emqtt

EMQTT

介绍

  • EMQ是基于 Erlang/OTP 平台开发的开源物联网 MQTT 消息服务器。

  • Erlang/OTP 是出色的软实时、低延时、分布式的语言平台。

  • MQTT 是轻量的、发布订阅模式的物联网消息协议。

  • EMQ 项目设计目标是承载移动终端或物联网终端海量 MQTT 连接,并实现在海量物联网设备间快速低延时消息路由:
    1、稳定承载大规模的 MQTT 客户端连接,单服务器节点支持50万到100万连接。
    2、分布式节点集群,快速低延时的消息路由,单集群支持1000万规模的路由。
    3、消息服务器内扩展,支持定制多种认证方式、高效存储消息到后端数据库。
    4、完整物联网协议支持,MQTT、MQTT-SN、CoAP、WebSocket 或私有协议支持。

Broker

Broker是什么?broker是代理,发布者发布消息到broker中,订阅都能过broker订阅消息,broker起到一个桥梁的作用,类似于tomcat、nginx

发布订阅

发布者既可以发布也可以订阅,同样的,订订阅者也是可以订阅和发布,这就实现了双方的通信,不难发现这在编程中很常见,是一个异步模式,这也是被用来做推送的原因,举个例子,我们打电话的时候,当对方不接听我们电话也就打不通,必须双方都在才可以通话,这是同步请求/回答,而mqtt则不需要对话的响应,这类似于发邮件,我们发邮件后,对方不一定就立即看到,等他有空的时候打开邮箱才看到,这就是异步发布/订阅的场景。

主题

MQTT 发布者与订阅者之间通过”主题”(Topic) 进行消息路由,MQTT 主题(Topic) 支持’+’, ‘#’的通配符,’+’通配一个层级,’#’通配多个层级(必须在末尾)。

  • MQTT 消息发布者只能向特定’名称主题’(不支持通配符)发布消息。
  • 订阅者通过订阅’过滤主题’(支持通配符)来匹配消息。

EMQ 消息服务器概念上更像一台网络路由器(Router)或交换机(Switch),而不是传统的企业级消息服务器(MQ)。相比网络路由器按 IP 地址或 MPLS 标签路由报文,EMQ 按主题树(Topic Trie)发布订阅模式在集群节点间路由 MQTT 消息。

订阅、主题、会话

一、订阅(Subscription)
订阅包含主题筛选器(Topic Filter)和最大服务质量(QoS)。订阅会与一个会话(Session)关联。一个会话可以包含多个订阅。每一个会话中的每个订阅都有一个不同的主题筛选器。

二、会话(Session)
每个客户端与服务器建立连接后就是一个会话,客户端和服务器之间有状态交互。会话存在于一个网络之间,也可能在客户端和服务器之间跨越多个连续的网络连接。

三、主题名(Topic Name)
连接到一个应用程序消息的标签,该标签与服务器的订阅相匹配。服务器会将消息发送给订阅所匹配标签的每个客户端。

四、主题筛选器(Topic Filter)
一个对主题名通配符筛选器,在订阅表达式中使用,表示订阅所匹配到的多个主题。

五、负载(Payload)
消息订阅者所具体接收的内容

服务质量

为了满足不同的场景,MQTT支持三种不同级别的服务质量(Quality of Service,QoS)为不同场景提供消息可靠性:

  • 级别0:尽力而为。消息发送者会想尽办法发送消息,但是遇到意外并不会重试。
  • 级别1:至少一次。消息接收者如果没有知会或者知会本身丢失,消息发送者会再次发送以保证消息接收者至少会收到一次,当然可能造成重复消息。
  • 级别2:恰好一次。保证这种语义肯待会减少并发或者增加延时,不过丢失或者重复消息是不可接受的时候,级别2是最合适的。

安装 启动

RPM安装:rpm -ivh emqttd-centos7-v2.1.2-1.el7.centos.x86_64.rpm
Erlang/OTP R19 依赖 lksctp-tools 库:yum install lksctp-tools

DEB安装:sudo dpkg -i emqttd-ubuntu16.04_v2.0_amd64.deb
Erlang/OTP R19 依赖 lksctp-tools 库:apt-get install lksctp-tools

通用程序包:unzip emqttd-centos7-v2.0.zip

  • 控制台调试模式启动 ./emqttd console
  • 守护进程模式启动 ./emqttd start
  • 查看运行状态 ./emqttd_ctl status
  • 停止服务器 ./emqttd stop

emqttd消息服务器启动后,会默认加载Dashboard插件,启动Web管理控制台。用户可通过Web控制台, 查看服务器运行状态、统计数据、客户端(Client)、会话(Session)、主题(Topic)、订阅(Subscription)。
控制台地址: http://127.0.0.1:18083,默认用户: admin,密码:public

插件

扩展插件通过 ‘bin/emqttd_ctl’ 管理命令行,或 Dashboard 控制台加载启用。

  • emq_plugin_template 插件模版与演示代码
  • emq_retainer Retain 消息存储插件
  • emq_modules Presence, Subscription 扩展模块插件
  • emq_dashboard Web 管理控制台,默认加载
  • emq_auth_clientid ClientId、密码认证插件
  • emq_auth_username 用户名、密码认证插件
  • emq_auth_ldap LDAP 认证插件
  • emq_auth_http HTTP 认证插件
  • emq_auth_mysql MySQL 认证插件
  • emq_auth_pgsql PostgreSQL 认证插件
  • emq_auth_redis Redis 认证插件
  • emq_auth_mongo MongoDB 认证插件
  • emq_sn MQTT-SN 协议插件
  • emq_coap CoAP 协议插件
  • emq_stomp Stomp 协议插件
  • emq_recon Recon 优化调测插件
  • emq_reloader 热升级插件(开发调试)

端口

EMQ 默认开启的 MQTT 服务 TCP 端口:

  • 1883 MQTT 协议端口
  • 8883 MQTT/SSL 端口
  • 8080 HTTP API 端口
  • 8083 MQTT/WebSocket 端口
  • 8084 MQTT/WebSocket/SSL 端口
  • 18083 Dashboard 管理控制台端口

EMQ 节点集群使用的 TCP 端口:

  • 4369 集群节点发现端口
  • 6369 集群节点控制通道

配置文件 环境变量

/var/log/emqttd 日志文件目录
/var/lib/emqttd/ 数据文件目录

etc/emq.conf EMQ 2.0 消息服务器配置文件
etc/acl.conf EMQ 2.0 默认ACL规则配置文件
etc/plugins/*.conf EMQ 2.0 各类插件配置文件

启动时通过环境变量设置EMQ节点名称、安全Cookie以及TCP端口号:

  • EMQ_NODE_NAME Erlang 节点名称,例如: emq@127.0.0.1
  • EMQ_NODE_COOKIE Erlang 分布式节点通信 Cookie
  • EMQ_MAX_PORTS Erlang 虚拟机最大允许打开文件 Socket 数
  • EMQ_TCP_PORT MQTT/TCP 监听端口,默认: 1883
  • EMQ_SSL_PORT MQTT/SSL 监听端口,默认: 8883
  • EMQ_WS_PORT MQTT/WebSocket 监听端口,默认: 8083
  • EMQ_WSS_PORT MQTT/WebSocket/SSL 监听端口,默认: 8084

EMQ 节点连接方式:

node.proto_dist = inet_tcp 或 inet6_tcp 或 inet_tls

Erlang 虚拟机主要参数说明:

  • node.process_limit Erlang 虚拟机允许的最大进程数,一个 MQTT 连接会消耗2个 Erlang 进程,所以参数值 > 最大连接数 * 2
  • node.max_ports Erlang 虚拟机允许的最大 Port 数量,一个 MQTT 连接消耗1个 Port,所以参数值 > 最大连接数
  • node.dist_listen_min Erlang 分布节点间通信使用 TCP 连接端口范围。注: 节点间如有防火墙,需要配置该端口段
  • node.dist_listen_max Erlang 分布节点间通信使用 TCP 连接端口范围。注: 节点间如有防火墙,需要配置该端口段

日志参数配置:

log.console = console 或 off 或 file 或 both
log.error.file = log/error.log
log.crash = on 或 off
log.syslog.level = error 或 debug, info, notice, warning, critical, alert, emergency

MQTT 协议参数配置

ClientId 最大允许长度
mqtt.max_clientid_len = 1024
MQTT 最大报文尺寸
mqtt.max_packet_size = 64KB
MQTT 客户端最大允许闲置时间(Socket 建立,未收到 CONNECT 报文)
mqtt.client.idle_timeout = 30
启用客户端连接统计
mqtt.client.enable_stats = off 或 on
强制 GC 设置(0表示禁用)
mqtt.conn.force_gc_count = 100

ACL规则

EMQ 消息服务器通过 ACL(Access Control List) 实现 MQTT 客户端访问控制。
EMQ 支持基于 etc/acl.conf 文件或 MySQL、 PostgreSQL 等插件的访问控制规则。
EMQ 消息服务器默认访问控制,在 etc/emq.conf 中设置:
是否开启匿名认证
mqtt.allow_anonymous = true

默认访问控制(ACL)文件
mqtt.acl_nomatch = allow
mqtt.acl_file = etc/acl.conf

ACL 规则定义在 etc/acl.conf,EMQ 启动时加载到内存。

ACL默认访问规则设置

etc/acl.conf 访问控制规则定义:
允许|拒绝 用户|IP地址|ClientID 发布|订阅 主题列表

%% 允许’dashboard’用户订阅 'KaTeX parse error: Expected 'EOF', got '#' at position 5: SYS/#̲' {allow, {user…SYS/#"]}.

%% 允许本机用户发布订阅全部主题
{allow, {ipaddr, “127.0.0.1”}, pubsub, ["$SYS/#", “#”]}.

%% 拒绝用户订阅’KaTeX parse error: Expected 'EOF', got '#' at position 4: SYS#̲'与'#'主题 {deny, …SYS/#", {eq, “#”}]}.

%% 上述规则无匹配,允许
{allow, all}.

默认规则只允许本机用户订阅’$SYS/#’与’#’
EMQ 消息服务器接收到 MQTT 客户端发布(PUBLISH)或订阅(SUBSCRIBE)请求时,会逐条匹配 ACL 访问控制规则,直到匹配成功返回 allow 或 deny。

MQTT 会话参数设置

mqtt.session.upgrade_qos = off
##可以一次“飞行”的最大QoS 1和2消息数,0表示没有限制。
mqtt.session.max_inflight = 32
##重新尝试重新传输QoS1 / 2消息的间隔。
mqtt.session.retry_interval = 20s
##等待PUBREL的最大数据包,0表示无限制
mqtt.session.max_awaiting_rel = 100
##正在等待PUBREL超时
mqtt.session.await_rel_timeout = 20s
##启用统计
mqtt.session.enable_stats = off
##过期时间
mqtt.session.expiry_interval = 2h

参数设置

MQTT 消息队列参数设置

  • mqueue.type 队列类型。simple: 简单队列,priority: 优先级队列
  • mqueue.priority 主题(Topic)队列优先级设置
  • mqueue.max_length 队列长度, infinity 表示不限制
  • mqueue.low_watermark 解除告警水位线
  • mqueue.high_watermark 队列满告警水位线
  • mqueue.qos0 是否缓存 QoS0 消息
    Broker 参数设置
    broker_sys_interval 设置系统发布 $SYS 消息周期:
    mqtt.broker.sys_interval = 60s
    发布订阅(PubSub)参数设置

PubSub池大小,默认值应该是调度程序编号。

mqtt.pubsub.pool_size = 8
mqtt.pubsub.by_clientid = true

异步订阅

mqtt.pubsub.async = true
桥接(Bridge)参数设置
mqtt.bridge.max_queue_len = 10000 队列大小
mqtt.bridge.ping_down_interval = 1s PING节点的时间间隔
插件配置目录
mqtt.plugins.etc_dir = etc/plugins/
用于存储加载的插件名称的文件
mqtt.plugins.loaded_file = data/loaded_plugins
MQTT Listeners 参数说明 :省略

分布集群:

Erlang/OTP 语言平台的分布式程序,由分布互联的 Erlang 运行系统组成,每个 Erlang 运行系统被称为节点(Node),节点(Node) 间通过 TCP 互联,消息传递的方式通信。
EMQ 消息服务器集群基于 Erlang/OTP 分布式设计,集群原理可简述为下述两条规则:
MQTT 客户端订阅主题时,所在节点订阅成功后广播通知其他节点:某个主题(Topic)被本节点订阅。
MQTT 客户端发布消息时,所在节点会根据消息主题(Topic),检索订阅并路由消息到相关节点。
节点桥接:
节点间桥接与集群不同,不复制主题树与路由表,只按桥接规则转发 MQTT 消息。

提供的认证插件

EMQ 消息服务器认证由一系列认证插件(Plugin)提供,系统支持按用户名密码、ClientID 或匿名认证。
系统默认开启匿名认证,通过加载认证插件可开启的多个认证模块组成认证链:

EMQ 2.0 版本提供的认证插件包括:

  • emq_auth_clientid ClientId 认证/鉴权插件
  • emq_auth_username 用户名密码认证/鉴权插件
  • emq_auth_ldap LDAP 认证/鉴权插件
  • emq_auth_http HTTP 认证/鉴权插件
  • emq_auth_mysql MySQ L认证/鉴权插件
  • emq_auth_pgsql Postgre 认证/鉴权插件
  • emq_auth_redis Redis 认证/鉴权插件
  • emq_auth_mongo MongoDB 认证/鉴权插件

HTTP 插件认证

etc/plugins/emq_auth_http.conf 配置 ‘super_req’, ‘auth_req’:

##Variables: %u = username, %c = clientid, %a = ipaddress, %P = password, %t = topic

auth.http.auth_req = http://127.0.0.1:8080/mqtt/auth
auth.http.auth_req.method = post
auth.http.auth_req.params = clientid=%c,username=%u,password=%P

auth.http.super_req = http://127.0.0.1:8080/mqtt/superuser
auth.http.super_req.method = post
auth.http.super_req.params = clientid=%c,username=%u

启用 HTTP 认证插件:
./bin/emqttd_ctl plugins load emq_auth_http

认证API

@RestController
@RequestMapping("/mqtt")
public class MqttUserCheckController {
 
	private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
	@RequestMapping(value = "/auth", method = RequestMethod.POST)
	public void checkUser(String clientid, String username, String password, HttpServletResponse response) {
		logger.info("普通用户;clientid:" + clientid + ";username:" + username + ";password:" + password);
		if (checkUser(clientid, username, password)) {
			response.setStatus(200);
		} else {
			response.setStatus(401);
		}
	}
}

$SYS-系统主题

EMQ 消息服务器周期性发布自身运行状态、MQTT 协议统计、客户端上下线状态到 $SYS/ 开头系统主题。

服务器版本、启动时间与描述消息

  • $SYS/brokers 集群节点列表
  • S Y S / b r o k e r s / SYS/brokers/ SYS/brokers/{node}/version EMQ 服务器版本
  • S Y S / b r o k e r s / SYS/brokers/ SYS/brokers/{node}/uptime EMQ 服务器启动时间
  • S Y S / b r o k e r s / SYS/brokers/ SYS/brokers/{node}/datetime EMQ 服务器时间
  • S Y S / b r o k e r s / SYS/brokers/ SYS/brokers/{node}/sysdescr EMQ 服务器描述

MQTT 客户端上下线状态消息:$SYS 主题前缀: S Y S / b r o k e r s / SYS/brokers/ SYS/brokers/{node}/clients/
主题:${clientid}/connected
‘connected’ 消息 JSON 数据:
{ipaddress: “127.0.0.1”, username: “test”, session: false, protocol: 3, connack: 0, ts: 1432648482}
‘disconnected’ 消息 JSON 数据:
{reason: normal, ts: 1432648486}

MQTT消息QoS

MQTT发布消息QoS保证不是端到端的,是客户端与服务器之间的。订阅者收到MQTT消息的QoS级别,最终取决于发布消息的QoS和主题订阅的QoS。

发布消息的QoS主题订阅的QoS接收消息的QoS
000
010
020
100
111
121
200
211
222

MQTT协议相关

MQTT会话(Clean Session)

MQTT客户端向服务器发起CONNECT请求时,可以通过’Clean Session’标志设置会话。
‘Clean Session’设置为0,表示创建一个持久会话,在客户端断开连接时,会话仍然保持并保存离线消息,直到会话超时注销。
‘Clean Session’设置为1,表示创建一个新的临时会话,在客户端断开时,会话自动销毁。

MQTT连接保活心跳

MQTT客户端向服务器发起CONNECT请求时,通过KeepAlive参数设置保活周期。
客户端在无报文发送时,按KeepAlive周期定时发送2字节的PINGREQ心跳报文,服务端收到PINGREQ报文后,回复2字节的PINGRESP报文。
服务端在1.5个心跳周期内,既没有收到客户端发布订阅报文,也没有收到PINGREQ心跳报文时,主动心跳超时断开客户端TCP连接。
emqttd消息服务器默认按最长2.5心跳周期超时设计。

例子

发送消息

public class MqttSendDemo {

    public void mqttPublish(){
        //设置clientid的保存形式,默认为以内存保存
        MemoryPersistence persistence = new MemoryPersistence();
        try {
            final MqttClient sampleClient = new MqttClient(ConfigInfo.mBorker, ConfigInfo.mMQTTClientId, persistence);
            //定义Mqtt参数
            final MqttConnectOptions connOpts = new MqttConnectOptions();
            connOpts.setUserName(ConfigInfo.mUserName);
            connOpts.setPassword(ConfigInfo.mPassWord.toCharArray());
            //设置客户端连接的serverURI列表
            connOpts.setServerURIs(new String[] { ConfigInfo.mBorker });
            //设置客户端和服务器是否应记住重新启动和重新连接的状态。
            connOpts.setCleanSession(true);
            //设置心跳间隔。
            connOpts.setKeepAliveInterval(90);
            //设置客户端是否会在连接丢失时自动尝试重新连接到服务器。
            connOpts.setAutomaticReconnect(true);
            sampleClient.setCallback(new MqttCallbackExtended(){

                //当与服务器的连接丢失时,将调用此方法。
                @Override
                public void connectionLost(Throwable throwable) {}

                //当消息从服务器到达时,将调用此方法。
                @Override
                public void messageArrived(String s, MqttMessage mqttMessage) throws Exception {}

                //当消息传递完成时调用,并且已收到所有确认。
                @Override
                public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {}

                //成功完成与服务器的连接时调用。
                @Override
                public void connectComplete(boolean b, String s) {}
            });
            //连接,并设置使用参数
            sampleClient.connect(connOpts);

            //发送消息
            MqttMessage message = new MqttMessage("将要发送的消息".getBytes());
            message.setQos(0);

            boolean connect = sampleClient.isConnected();
            if(connect){
                //设置Topic
                String strTopic = "app/data/xxx";
                try{
                    //发送消息
                    sampleClient.publish(strTopic, message);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        } catch (MqttException e) {
            e.printStackTrace();
        }
    }
}


接收消息

public class MqttRceiveDemo {

    public void mqttSubscribe(){
        //设置clientid的保存形式,默认为以内存保存
        MemoryPersistence persistence = new MemoryPersistence();
        try {
            final MqttClient sampleClient = new MqttClient(ConfigInfo.mBorker, ConfigInfo.mMQTTClientId, persistence);
            //定义Mqtt参数
            final MqttConnectOptions connOpts = new MqttConnectOptions();
            connOpts.setUserName(ConfigInfo.mUserName);
            connOpts.setPassword(ConfigInfo.mPassWord.toCharArray());
            //设置客户端连接的serverURI列表
            connOpts.setServerURIs(new String[] { ConfigInfo.mBorker });
            //设置客户端和服务器是否应记住重新启动和重新连接的状态。
            connOpts.setCleanSession(true);
            //设置心跳间隔。
            connOpts.setKeepAliveInterval(90);
            //设置客户端是否会在连接丢失时自动尝试重新连接到服务器。
            connOpts.setAutomaticReconnect(true);
            sampleClient.setCallback(new MqttCallbackExtended(){

                //当与服务器的连接丢失时,将调用此方法。
                @Override
                public void connectionLost(Throwable throwable) {}

                //当消息从服务器到达时,将调用此方法。
                @Override
                public void messageArrived(String topic, MqttMessage message) throws Exception {
                    // subscribe后得到的消息会执行到这里面
                    System.out.println("接收消息主题 : " + topic);
                    System.out.println("接收消息Qos : " + message.getQos());
                    System.out.println("接收消息内容 : " + new String(message.getPayload()));
                }

                //当消息传递完成时调用,并且已收到所有确认。
                @Override
                public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {}

                //成功完成与服务器的连接时调用。reconnect -如果为true,则表示连接是自动重新连接的结果。
                @Override
                public void connectComplete(boolean reconnect, String serverURI) {
                    //订阅Topic
                    sampleClient.subscribe(ConfigInfo.mEMQTTTopic, ConfigInfo.mEMQTTQos);
                }
            });
            //连接,并设置使用参数
            sampleClient.connect(connOpts);
        } catch (MqttException e) {
            e.printStackTrace();
        }
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值