MQTT & EMQ X

mqtt协议

  MQTT协议(消息队列遥测传输协议) 是基于 Publish/Subscribe 模式的一种低开销、低带宽占用的即时通讯协议。是基于TCP协议传输的;他也有UDP版本,叫做MQTT-SN。

Qos (消息服务质量)

  消息服务质量 又称 可靠传输保证;他又有三种消息发布服务质量
    支持 QoS0 "至多一次"传输(如果Bit 1和Bit 2都为0,表示QoS 0)
    支持 QoS1 "至少一次"传输(如果Bit 1为1,表示QoS 1)
    支持 QoS2 "只有一次"传输(如果Bit 2为1,表示QoS 2)
  订阅者收到MQTT消息的QoS级别,最终取决于发布消息的QoS和主题订阅的QoS中低的那个

协议原理

  生产者和消费者是通过订阅主题来通信,主题是以 ‘/’ 为分隔符区分不同的层级。协议方法有15个动作
    (1)CONNECT:客户端连接到服务器
    (2)CONNACK:连接确认
    (3)PUBLISH:发布消息
    (4)PUBACK:发布确认
    (5)PUBREC:发布的消息已接收
    (6)PUBREL:发布的消息已释放
    (7)PUBCOMP:发布完成
    (8)SUBSCRIBE:订阅请求
    (9)SUBACK:订阅确认
    (10)UNSUBSCRIBE:取消订阅
    (11)UNSUBACK:取消订阅确认
    (12)PINGREQ:客户端发送心跳
    (13)PINGRESP:服务端心跳响应
    (14)DISCONNECT:断开连接
    (15)AUTH:认证

  mqtt协议的网络传输开销小,主要是因为其简洁的请求报文(1个字节的固定报头 和 2个字节的心跳报文)。协议数据包:由三部分组成:
    固定报文头:表示数据包类型及数据包的分组,如连接,发布,订阅,心跳等。上面的15个动作都是数据包类型
    可变报文头:有些固定报文头是没有可变报文头的,
    报文体:有些固定报文头是没有报文体的,报文体就是消息体。

  mqtt之所以可以在弱网下发送消息就是因为简洁的报文+qos

EMQ X

  EMQX 是 MQTT Broker 的一种实现,属于数据采集这一层。
  前端的硬件通过 MQTT 协议与位于数据采集层的 EMQ X 交互,通过 EMQ X 提供的数据接口,将数据保存到后台的持久化平台中(各种关系型数据库和 NOSQL 数据库),或者流式数据处理框架等,上层应用通过这些数据分析后得到的结果呈现给最终用户。

安装

拉取镜像
docker pull emqx/emqx:v4.1.0
运行容器就直接可以启动了
docker run -tid --name emqx -p 1883:1883 -p 8083:8083 -p 8081:8081 -p 8883:8883 -p 8084:8084 -p 18083:18083 emqx/emqx:v4.1.0

地址:http://192.168.200.129:18083
默认用户名:admin,默认密码:public

常用命令:

  启动 emqx start
  查看状态 emqx_ctl status
  停止 emqx stop
  重启 emqx restart
  卸载 rpm -e emqx

  

延迟队列

开启延迟队列

  对正常的topic前面加两个层级,就可以成为延迟队列了。($delayed是关键字,普通队列不要用哦)
  生产者:$delayed/时间(秒)/topic
  消费者:直接监听topic就好了

共享队列(点对点)

queue

EMQ X 支持两种格式的共享订阅前缀:

  示例 前缀 真实主题名
  $queue/t/1 $queue/ t/1
  $share/abc/t/1 $share/abc t/1
  多个消费者监听 $queue/t/1,生产者发送 t/1

 

  默认是随机策略,设置轮询策略

  1. 进入容器 docker exec -it 容器id /bin/sh

  2. 编辑配置 vi emqx.conf , /开头 直接搜索指定配置,把random替换成轮询

  /broker.shared_subscription_strategy

 

  3. 最后保存重启 cd ../bin/ && emqx restart

多个此时有三个消费者:

  a监听 $queue/t/1

  b监听 $queue/t/1

  c监听 t/1
  此时如果有发送者往 t/1 发送3条消息,那么c会收到3条,而ab会以轮询的方式共享3条。

share

EMQ X 会向两个群组 g1 和 g2 同时发送 msg1

s1,s2,s3 中只有一个会收到 msg1

s4,s5 中只有一个会收到 msg1

静态代理订阅

就是我们的broker 不用指定订阅的topic,但是配置规则后,默认就会监听这些 topic 了

1. 开启静态订阅

2. 指定订阅规则

  在 emqx.conf 配置规则并重启。比如我这里就是配置了4个规则,指定了他们的队列和qos等级。

在配置代理订阅的主题时,EMQ X 提供了 %c 和 %u 两个占位符供用户使用,EMQ X 会在执行代理订阅时将配置中的 %c 和 %u 分别替换为客户端的 Client ID 和 Username,需要注意的是,%c 和 %u 必须占用一整个主题层级。(在连接emqx broker的时候会分配一个clientId,username是自己自定义的)

module.subscription.<number>.topic = <topic>

module.subscription.<number>.qos = <qos>

<number>表示第几个规则;<topic>表示队列;<qos>是质量等级

  这种方式每次都要改配置,比较麻烦;目前只有收费版才有 动态订阅功能。

保留消息

  生产者发送消息后,消费者再去监听这个消息,是拿不到的。只有设置为“保留消息”,消费者才可以拿到。如果往一个队列里面发送了多条消息,消费者此时监听只能拿到最近的一条消息。

  我们还可以自定义保留规则

vi etc/plugins/emqx_retainer.conf

安全认证

  1. 开启http认证

  1. 配置 http 认证 地址,请求方式,参数

3. 开发认证服务接口(我们连接emqx , emqx 就会根据我们上面的配置信息发送认证请求)

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import java.util.HashMap;

/**
 * @title: HTML 验证
 * @author: jay.wu
 * @date: 2022/2/21 18:57
 */
@Slf4j
@RestController
@RequestMapping("/mqtt")
public class AuthController {

    private HashMap<String,String> users;

    @PostConstruct
    public void init(){
        users = new HashMap<>();
        users.put("user","123456");
        users.put("emq-client2","123456");//testtopic/#
        users.put("emq-client3","123456");// testtopic/123
        users.put("admin","admin");
    }


    @PostMapping("/auth")
    public ResponseEntity auth(@RequestParam("clientid") String clientid,
                               @RequestParam("username") String username,
                               @RequestParam("password") String password){
        log.info("emqx http认证组件开始调用任务服务完成认证,clientid={},username={},password={}",clientid,username,password);

        String value = users.get(username);
        if(StringUtils.isEmpty(value)){
            return new ResponseEntity(HttpStatus.UNAUTHORIZED);
        }
        if(!value.equals(password)){
            return new ResponseEntity(HttpStatus.UNAUTHORIZED);
        }
        return new ResponseEntity(HttpStatus.OK);
    }


    @PostMapping("/superuser")
    public ResponseEntity superuser(@RequestParam("clientid") String clientid,
                                    @RequestParam("username") String username){
        log.info("emqx 查询是否是超级用户,clientid={},username={}",clientid,username);
        if(clientid.contains("admin") || username.contains("admin")){
            log.info("用户{}是超级用户",username);
            return new ResponseEntity(HttpStatus.OK);
        }else {
            log.info("用户{}不是超级用户",username);
            return new ResponseEntity(HttpStatus.UNAUTHORIZED);
        }

    }

    @PostMapping("/acl")
    public ResponseEntity acl(@RequestParam("access")int access,
                              @RequestParam("username")String username,
                              @RequestParam("clientid")String clientid,
                              @RequestParam("ipaddr")String ipaddr,
                              @RequestParam("topic")String topic,
                              @RequestParam("mountpoint")String mountpoint){
        log.info("EMQX发起客户端操作授权查询请求,access={},username={},clientid={},ipaddr={},topic={},mountpoint={}",
                access,username,clientid,ipaddr,topic,mountpoint);

        if(username.equals("emq-client2") && topic.equals("testtopic/#") && access  == 1){
            log.info("客户端{}有权限订阅{}",username,topic);
            return new ResponseEntity(HttpStatus.OK);
        }
        if(username.equals("emq-client3") && topic.equals("testtopic/123") && access == 2){
            log.info("客户端{}有权限向{}发布消息",username,topic);
            return new ResponseEntity(HttpStatus.OK);
        }
        log.info("客户端{},username={},没有权限对主题{}进行{}操作",clientid,username,topic,access==1?"订阅":"发布");
        return new ResponseEntity(HttpStatus.UNAUTHORIZED);
        //return new ResponseEntity(HttpStatus.OK);
    }

}

 

 

 

 

 

 

 

 

 
posted @ 2022-02-12 16:16  吴磊的  阅读(517)  评论(0编辑  收藏  举报
//生成目录索引列表