• 关于MQTT的应用场景对接


     

    通过 CTWing中国电信物联网开放平台 实现AEP设备接入(MQTT协议)

    AEP设备接入文档  

     

    Maven依赖

    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    </dependency>
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    </dependency>
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.54</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.eclipse.paho/org.eclipse.paho.client.mqttv3 -->
    <dependency>
    <groupId>org.eclipse.paho</groupId>
    <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
    <version>1.2.0</version>
    </dependency>
    <!-- 第三方util -->
    <dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>4.1.2</version>
    </dependency>
    <!--aep-->
    <dependency>
    <groupId>com.ctg.ag</groupId>
    <artifactId>ag-sdk-biz-64993.tar.gz</artifactId>
    <version>20210714.173418-SNAPSHOT</version>
    </dependency>
    <dependency>
    <groupId>com.ctg.ag</groupId>
    <artifactId>ctg-ag-sdk-core</artifactId>
    <version>2.5.0-SNAPSHOT</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
    <dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.5</version>
    </dependency>
    <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.1</version>
    </dependency>
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
    </dependency>
    <!-- HikariCP连接池 -->
    <dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    </dependency>
    <!--pagehelper 分页组件 -->
    <dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.2.10</version>
    </dependency>
    <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.0.0</version>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-stream</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-mqtt</artifactId>
    </dependency>
    </dependencies>

    配置文件application.yml

    server:
      port: 110
    spring:
      main:
        allow-bean-definition-overriding: true
      basePackage: com.test.device
      resources:
        static-locations: classpath:/static, classpath:/templates
      jackson:
        #参数意义:
        #JsonInclude.Include.ALWAYS       默认
        #JsonInclude.Include.NON_DEFAULT   属性为默认值不序列化
        #JsonInclude.Include.NON_EMPTY     属性为 空(””) 或者为 NULL 都不序列化
        #JsonInclude.Include.NON_NULL      属性为NULL  不序列化
        default-property-inclusion: ALWAYS
        time-zone: GMT+8
        date-format: yyyy-MM-dd HH:mm:ss
      datasource:
        type: com.zaxxer.hikari.HikariDataSource
        url: jdbc:mysql://127.0.0.1:3306/ctwing_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai
        username: root
        password: root
        # 连接池配置
        hikari:
          # 等待连接池分配连接的最大时长(毫秒),超过这个时长还没可用的连接则发生SQLException, 缺省:30秒
          connection-timeout: 30000
          # 一个连接idle状态的最大时长(毫秒),超时则被释放(retired),缺省:10分钟
          idle-timeout: 600000
          # 一个连接的生命时长(毫秒),超时而且没被使用则被释放(retired),缺省:30分钟,建议设置比数据库超时时长少30秒,参考MySQL
          max-lifetime: 1800000
          # 连接池中允许的最大连接数。缺省值:10;推荐的公式:((core_count * 2) + effective_spindle_count)
          maximum-pool-size: 2
          minimum-idle: 1
      servlet:
        multipart:
          max-file-size: 10MB
          max-request-size: 10MB
    mybatis:
      type-aliases-package: ${spring.basePackage}
      mapper-locations: classpath:mapper/*/*.xml
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    logging:
      config: classpath:config/logback.xml
    log:
      path: C:\logs # 日志存放路径
      enableLogType: 1234   # 日志类型  增改删查
    timing:
    http_whether: 0 #1开启,非1不开启定时(0)
    mqtt_whether: 0 #1开启,非1不开启定时(0)
    concurrent: 1 #循环并发下发次数
    ctwing:
      mqtt:
        productId: ******  # 产品Id,平台产品信息中获取
        clientId: ******   #必填。填写平台添加设备时生成的设备ID。
        userName: ****** #必填。建议填写为CTWing平台用户名。
        passWord: ******* #必填。填写平台为设备自动分配的特征串。
        broker: tcp://mqtt.ctwing.cn:1883 #mq地址
        qos: 1 #质量等级
        appKey: *******    #aep应用中配置
        appSecret: ******* #aep应用中配置
        masterKey: *******   #MasterKey为平台上创建产品时自动生成,可在产品概况中查询。

    Aep发布订阅

    import com.alibaba.fastjson.JSON;import org.eclipse.paho.client.mqttv3.*;
    import java.util.HashMap;
    import java.util.Map;
    
    public class MqttAepPaho {
    
        final Map<String,MqttClient> clientMap=new HashMap<>();
        static final Map<String,MqttAepPaho> aepPahoMap=new HashMap<>();
        private static MqttAepPaho instance;
        MQTTConfig mqttConfig;
        TimingConfig timingConfig;
    
        private MqttAepPaho (){}
    
        public MqttAepPaho(MQTTConfig mqttConfig, TimingConfig timingConfig) {
            this.mqttConfig = mqttConfig;
            this.timingConfig = timingConfig;
        }
    
        //运行时加载对象
        public static MqttAepPaho getInstance(DeviceInfo deviceInfo,MQTTConfig mqttConfig, TimingConfig timingConfig){
            instance = null;
            instance = aepPahoMap.get(deviceInfo.getDeviceId());
            if (instance == null) {
                //todo 重新连接
                System.out.println("重新连接");
                new MqttAepPaho(mqttConfig,timingConfig).start(deviceInfo);
            }
            return instance;
        }
    
        /**
         * AEP 发布
         * @param deviceInfo 设备ID+设备特征串
         * @param topic 服务标识 平台会生成相应Topic,Publish报文的Topic字段填写相应服务标识
         * @param map 服务标识-参数
         */
        public void topic(DeviceInfo deviceInfo,String topic, Map map){
            try {
                MqttClient client=clientMap.get(deviceInfo.getDeviceId());
                if(client==null){
                    client=connect(deviceInfo);
                }
                System.out.println("当前发布获取实例:"+deviceInfo.getDeviceId()+"---"+client);
                //在另一个线程中发送消息
                publishMsg(topic, JSON.toJSONString(map), mqttConfig.getQos(), client);
                //断开服务连接
                //client.disconnect();
                //System.out.println("断开服务连接");
                System.out.println("已完成");
            } catch(MqttException me) {
                System.out.println("reason "+me.getReasonCode());
                System.out.println("msg "+me.getMessage());
                System.out.println("loc "+me.getLocalizedMessage());
                System.out.println("cause "+me.getCause());
                System.out.println("exception "+me);
                me.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                System.out.println("正常退出");
                //System.exit(0);
            }
        }
    
        //推送
        private void publishMsg(String topic, String content, int qos, MqttClient client) throws MqttException {
            //循环发送10次消息
            for (int times =0 ;times<timingConfig.getConcurrent(); times++) {
                System.out.println(String.format("%d 循环发送消息: %s", times, content));
                //创建消息内容
                MqttMessage message = new MqttMessage(content.getBytes());
                //设置质量级别
                message.setQos(qos);
                //发送消息
                client.publish(topic, message);
                System.out.println("Message published");
            }
        }
    
        /**
         * AEP订阅
         * @param deviceInfo 设备ID+设备特征串
         */
        public void start(DeviceInfo deviceInfo) {
            try {
                MqttClient client=clientMap.get(deviceInfo.getDeviceId());
                if(client==null){
                    client=connect(deviceInfo);
                }
                System.out.println("当前订阅获取实例:"+deviceInfo.getDeviceId()+"---"+client);
                //订阅消息
                int[] Qos  = CommonConstants.Qos;
                String[] topic1 = CommonConstants.topic;
                client.subscribe(topic1, Qos);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        //连接
        private MqttClient connect(DeviceInfo deviceInfo) throws Exception{
            // host为主机名,clientId即连接MQTT的客户端ID,一般以客户端唯一标识符表示,MemoryPersistence设置clientId的保存形式,默认为以内存保存
            MqttClient client = new MqttClient(mqttConfig.getBroker(), deviceInfo.getDeviceId(), null);
            // MQTT的连接设置
            MqttConnectOptions options = new MqttConnectOptions();
            // 设置是否清空session,这里如果设置为false表示服务器会保留客户端的连接记录,这里设置为true表示每次连接到服务器都以新的身份连接
            options.setCleanSession(true);
            // 设置连接的用户名
            options.setUserName(mqttConfig.getUserName());
            // 设置连接的密码
            options.setPassword(deviceInfo.getToken().toCharArray());
            // 设置超时时间 单位为秒
            options.setConnectionTimeout(10);
            // 设置会话心跳时间 单位为秒 服务器会每隔1.5*20秒的时间向客户端发送个消息判断客户端是否在线,但这个方法并没有重连的机制
            options.setKeepAliveInterval(90);
            // 设置回调
            client.setCallback(new MqttAepCallback(deviceInfo,this));
            client.connect(options);
            //缓存实例
            clientMap.put(deviceInfo.getDeviceId(),client);
            aepPahoMap.put(deviceInfo.getDeviceId(),this);
            instance=this;
            return client;
        }
    
    }

    配置类

    import lombok.Getter;
    import lombok.Setter;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.stereotype.Component;
    
    @ConfigurationProperties(prefix = "timing", ignoreInvalidFields = true)
    @Getter
    @Setter
    @Component
    public class TimingConfig {
        /** 是否开启定时上报 */
        private int http_whether;
        private int mqtt_whether;
        /** 循环并发下发次数*/
        private int concurrent;
    }
    import lombok.Getter;
    import lombok.Setter;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.stereotype.Component;
    
    @ConfigurationProperties(prefix = "ctwing.mqtt", ignoreInvalidFields = true)
    @Getter
    @Setter
    @Component
    public class MQTTConfig {
        /** 必填。建议填写为CTWing平台用户名。 */
        private String userName;
        /** 必填。填写平台为设备自动分配的特征串。 */
        private String passWord;
        /* mq地址*/
        private String broker;
        /*质量等级 */
        private int qos;
        /*产品Id,平台产品信息中获取*/
        private int productId;
        /*必填。填写平台添加设备时生成的设备ID。*/
        private String clientId;
        /*aep应用中配置*/
        private String appSecret;
        /*aep应用中配置*/
        private String appKey;
        /*MasterKey为平台上创建产品时自动生成,可在产品概况中查询。*/
        private String masterKey;
    }

    订阅回调

    import lombok.extern.slf4j.Slf4j;
    import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
    import org.eclipse.paho.client.mqttv3.MqttCallback;
    import org.eclipse.paho.client.mqttv3.MqttException;
    import org.eclipse.paho.client.mqttv3.MqttMessage;
    import java.nio.charset.StandardCharsets;
    
    @Slf4j
    public class MqttAepCallback implements MqttCallback {
    
        private DeviceInfoDao deviceInfoDao = ContextHolder.getBean(DeviceInfoDao.class);private DeviceInfo deviceInfo;
        private MqttAepPaho mqttAepPaho;
    
        public MqttAepCallback() {
        }
    
        public MqttAepCallback(DeviceInfo deviceInfo,MqttAepPaho mqttAepPaho) {
            this.deviceInfo = deviceInfo;
            this.mqttAepPaho = mqttAepPaho;
        }
    
        // 连接丢失后,一般在这里面进行重连
        @Override
        public void connectionLost(Throwable arg0) {
            log.info("Connection Lost:{}",arg0.getMessage());
        }
    
        // subscribe后得到的消息会执行到这里面
        @Override
        public void messageArrived(String s, MqttMessage mqttMessage) throws MqttException {
            log.info("当前clientId:{},特征串:{}",deviceInfo.getDeviceId(),deviceInfo.getToken());
            log.info("接收消息messageId:{}", mqttMessage.getId());
            log.info("接收消息Qos:{}", mqttMessage.getQos());
            log.info("接收消息内容:{} from 接收消息主题:{}",mqttMessage,s);
            String ss = new String(mqttMessage.getPayload(), StandardCharsets.UTF_8);
            log.info("字节转Json:{}",ss);
            ReceiptStrategyContext receiptStrategyContext = new ReceiptStrategyContext();
            if(s.equals(TopicEnum.open_cmd.getTopic())){
                receiptStrategyContext.setReceiptHandleStrategy(new Mqtt8001ReceiptHandleStrategy(mqttAepPaho,deviceInfo,deviceInfoDao));
            }else{
                log.info("未知订阅主题");
                return;
            }
            receiptStrategyContext.handleReceipt(new Receipt(){{setTopic(s);setMessage(ss);}});
        }
    
        //消息发送成功后,调用
        @Override
        public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
            log.info("消息发送成功后,调用");
            if(iMqttDeliveryToken.isComplete()){
                log.info("Delivery a Msg to Topic:{}",iMqttDeliveryToken.getTopics()[0]);
            }
        }
    }

    回调策略

    /**
     * 上下文类,持有策略接口
     */
    public class ReceiptStrategyContext {
    
        private ReceiptHandleStrategy receiptHandleStrategy;
    
        //设置策略接口
        public void setReceiptHandleStrategy(ReceiptHandleStrategy receiptHandleStrategy) {
            this.receiptHandleStrategy = receiptHandleStrategy;
        }
        public void handleReceipt(Receipt receipt){
            if (receiptHandleStrategy != null) {
                receiptHandleStrategy.handleReceipt(receipt);
            }
        }
    }
    public interface ReceiptHandleStrategy {
    
        //回执处理策略接口
        void handleReceipt(Receipt receipt);
    }
    @Slf4j
    public class Mqtt8001ReceiptHandleStrategy implements ReceiptHandleStrategy {
    
        private MqttAepPaho mqttAepPaho;
        private DeviceInfo deviceInfo;
        private DeviceInfoDao deviceInfoDao;
    
        public Mqtt8001ReceiptHandleStrategy(MqttAepPaho mqttAepPaho,DeviceInfo deviceInfo,DeviceInfoDao deviceInfoDao) {
            this.mqttAepPaho = mqttAepPaho;
            this.deviceInfo = deviceInfo;
            this.deviceInfoDao = deviceInfoDao;
        }
    
        @Override
        public void handleReceipt(Receipt receipt) {
            log.info("解析报文MQTT8001:{},当前实例:{},当前设备信息:{}",receipt.getMessage(),mqttAepPaho,deviceInfo);
            AepRespVo<JSONObject> aepRespVo= JSON.parseObject(receipt.getMessage(), AepRespVo.class);
            CmdRespReport report=new CmdRespReport(){{setTaskId(aepRespVo.getTaskId());setResultPayload(new CmdResp());}};
            log.info("设备回复AEP指令响应");
            mqttAepPaho.topic(deviceInfo, TopicEnum.cmd_resp.getTopic(), MapUtil.beanToMap(report));
            OpenCmd openCmd=JSON.toJavaObject(aepRespVo.getPayload(),OpenCmd.class);
            //@todo 是否判断开门失败成功? 上报开门记录?存储开门日志?
            log.info("远程开门指令开始上报远程开门结果");
            RemoteOpenReport remoteOpenReport=new RemoteOpenReport();
            remoteOpenReport.setMsg_id(openCmd.getMsg_id());
            remoteOpenReport.setRemoteopen_params(JSON.toJSONString(openCmd));
            mqttAepPaho.topic(deviceInfo,TopicEnum.remoteopen_report.getTopic(),MapUtil.beanToMap(remoteOpenReport));
        }
    }

    DTO

    import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
    import lombok.Data;
    
    @Data
    @JsonIgnoreProperties(ignoreUnknown = true)
    public class AepRespVo <T>{
    
        private int taskId;
        private T payload;
    }
    import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
    import lombok.Data;
    
    /**
     * 订阅指令信息
     */
    @Data
    @JsonIgnoreProperties(ignoreUnknown = true)
    public class Receipt {
    
        //回执信息(json字符串)
        String message;
        //回执主题(`open_cmd、、、、、、、、、、`)
        String topic;
    }

    指令下发

        @Scheduled(cron = "*/5 * * * * ?")
        public void mqttConfigureTasks(){
            //@todo 固定上报yml中配置的设备ID,批量上报设备需获取设备ID+特征串
            if(config.getMqtt_whether()==1){
                List<DeviceInfo> list=deviceInfoDao.findDeviceByToken(new DeviceInfo());
                list.forEach(i->{
                    MqttReport(i,2);
                });
            }else{
                log.info("未开启MQTT定时器");
            }
        }
    
        public void MqttReport(DeviceInfo deviceInfo,int id){
            MqttBusinessDto dto=new MqttBusinessDto();
            switch (id) {case 2://心跳
                    dto.setTopic(TopicEnum.heartbeat.getTopic());
                    dto.setMap(MapUtil.beanToMap(new Heartbeat(){{setIMEI("001");}}));
                    break;default:
                    break;
            }
            MqttAepPaho.getInstance(deviceInfo,mqttConfig,config).topic(deviceInfo,dto.getTopic(),dto.getMap());
        }

    DTO

    import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
    import lombok.Data;
    import java.util.Map;
    
    @Data
    @JsonIgnoreProperties(ignoreUnknown = true)
    public class MqttBusinessDto {
    
        //设备Id,平台设备信息管理中获取。
        private String clientId;
        //设备特征串。平台生成,每个设备唯一
        private String passWord;
        //服务标识
        private String topic;
        //业务数据。仅支持JSON格式。非透传产品需根据服务定义填写。
        private Map map;
    }
    import java.util.Date;
    import lombok.Getter;
    import lombok.Setter;
    import lombok.NoArgsConstructor;
    import org.springframework.format.annotation.DateTimeFormat;
    
    @Getter
    @Setter
    @NoArgsConstructor
    public class DeviceInfo {
    
        private static final long serialVersionUID = 1L;
    
        /** 设备id */
        private String deviceId = "";
        /** 设备编号 */
        private String deviceSn = "";
        /** 终端名称 */
        private String deviceName = "";
        /** 租户id */
        private String tenantId = "";
        /** 产品id */
        private int productId;
        /** 版本信息 */
        private String firmwareVersion = "";
        /** 设备状态 0:已注册,1:已激活,2:已注销 */
        private int deviceStatus;
        /** 激活时间 */
        @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
        private Date activeTime;
        /** 注销时间 */
        @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
        private Date logoutTime;
        /** 设备在线状态 */
        private int netStatus;
        /** 设备最后上线时间 */
        @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
        private Date onlineAt;
        /** 设备最后下线时间 */
        @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
        private Date offlineAt;
        /** 设备所在产品协议:1.T-LINK协议  2.MQTT协议  3.LWM2M协议  4.TUP协议  5.HTTP协议  6.JT/T808  7.TCP协议  8.私有TCP(网关子设备协议)  9.私有UDP(网关子设备协议)  10.网关产品MQTT(网关产品协议)  11.南向云 */
        private int productProtocol;
        /** 设备特征串 */
        private String token = "";
        /** 枚举值 : 0--正常 1--禁用 */
        private int disable_status;
    }

    部分上报接收MQ消息的实体Report和指令下发的实体Cmd根据AEP平台中定义的通讯来组装。


     MQTT简介

    MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的“轻量级”通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年发布。MQTT最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。

    MQTT是一个基于客户端-服务器的消息发布/订阅传输协议。MQTT协议是轻量、简单、开放和易于实现的,这些特点使它适用范围非常广泛。在很多情况下,包括受限的环境中,如:机器与机器(M2M)通信和物联网(IoT)。其在,通过卫星链路通信传感器、偶尔拨号的医疗设备、智能家居、及一些小型化设备中已广泛使用。

    MQTT特性

    MQTT协议工作在低带宽、不可靠的网络的远程传感器和控制设备通讯而设计的协议,它具有以下主要的几项特性:

    1. 使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合。这一点很类似于XMPP,但是MQTT的信息冗余远小于XMPP,,因为XMPP使用XML格式文本来传递数据。
    2. 对负载内容屏蔽的消息传输。
    3. 使用TCP/IP提供网络连接。主流的MQTT是基于TCP连接进行数据推送的,但是同样有基于UDP的版本,叫做MQTT-SN。这两种版本由于基于不同的连接方式,优缺点自然也就各有不同了。
    4. 有三种消息发布服务质量:“至多一次”,消息发布完全依赖底层TCP/IP网络。会发生消息丢失或重复。这一级别可用于如下情况,环境传感器数据,丢失一次读记录无所谓,因为不久后还会有第二次发送。这一种方式主要普通APP的推送,倘若你的智能设备在消息推送时未联网,推送过去没收到,再次联网也就收不到了。“至少一次”,确保消息到达,但消息重复可能会发生。“只有一次”,确保消息到达一次。在一些要求比较严格的计费系统中,可以使用此级别。在计费系统中,消息重复或丢失会导致不正确的结果。这种最高质量的消息发布服务还可以用于即时通讯类的APP的推送,确保用户收到且只会收到一次。
    5. 小型传输,开销很小(固定长度的头部是2字节),协议交换最小化,以降低网络流量。这就是为什么在介绍里说它非常适合“在物联网领域,传感器与服务器的通信,信息的收集”,要知道嵌入式设备的运算能力和带宽都相对薄弱,使用这种协议来传递消息再适合不过了。
    6. 使用Last Will和Testament特性通知有关各方客户端异常中断的机制。Last Will:即遗言机制,用于通知同一主题下的其他设备发送遗言的设备已经断开了连接。Testament:遗嘱机制,功能类似于Last Will。

    MQTT协议实现方式

    实现MQTT协议需要客户端和服务器端通讯完成,在通讯过程中,MQTT协议中有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。MQTT传输的消息分为:主题(Topic)和负载(payload)两部分:

    1. Topic,可以理解为消息的类型,订阅者订阅(Subscribe)后,就会收到该主题的消息内容(payload);
    2. payload,可以理解为消息的内容,是指订阅者具体要使用的内容。

    更多简介传送门


     MQTT搭建


     MQTT协议之订阅及发布(使用paho-mqtt-client或mqttv3实现)

    消息接收回调类

    import org.eclipse.paho.client.mqttv3.MqttCallback;  
    import org.eclipse.paho.client.mqttv3.MqttDeliveryToken;  
    import org.eclipse.paho.client.mqttv3.MqttMessage;  
    import org.eclipse.paho.client.mqttv3.MqttTopic;  
      
    /**  
     * 发布消息的回调类  
     *   
     * 必须实现MqttCallback的接口并实现对应的相关接口方法  
     * CallBack 类将实现 MqttCallBack。每个客户机标识都需要一个回调实例。在此示例中,构造函数传递客户机标识以另存为实例数据。在回调中,将它用来标识已经启动了该回调的哪个实例。  
     * 必须在回调类中实现三个方法:  
     *   
     *  public void messageArrived(MqttTopic topic, MqttMessage message)  
     *  接收已经预订的发布。  
     *   
     *  public void connectionLost(Throwable cause)  
     *  在断开连接时调用。  
     *   
     *  public void deliveryComplete(MqttDeliveryToken token))  
     *  接收到已经发布的 QoS 1 或 QoS 2 消息的传递令牌时调用。由 MqttClient.connect 激活此回调。   
     */    
    public class PushCallback implements MqttCallback {  
      
        public void connectionLost(Throwable cause) {  
            // 连接丢失后,一般在这里面进行重连  
            System.out.println("连接断开,可以做重连");  
        }  
      
        public void deliveryComplete(MqttDeliveryToken token) {  
            // publish后会执行到这里  
            System.out.println("deliveryComplete---------"+ token.isComplete());  
        }  
      
        public void messageArrived(MqttTopic topic, MqttMessage message) throws Exception {  
            // subscribe后得到的消息会执行到这里面  
            System.out.println("接收消息主题:"+topic.getName());  
            System.out.println("接收消息Qos:"+message.getQos());  
            System.out.println("接收消息内容:"+new String(message.getPayload()));  
        }  
    }  

    服务端消息发布

    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.MqttPersistenceException;  
    import org.eclipse.paho.client.mqttv3.MqttTopic;  
    import org.eclipse.paho.client.mqttv3.internal.MemoryPersistence;  
      
    public class Server {  
      
        public static final String HOST = "tcp://127.0.0.1:1883";  
      
        public static final String TOPIC = "主题";  
        private static final String clientid ="server_clientid";   
      
        private MqttClient client;  
        private MqttTopic topic;  
        private String userName = "test";  
        private String passWord = "test";
        private MqttMessage message;  
      
        public Server() throws MqttException {  
            //MemoryPersistence设置clientid的保存形式,默认为以内存保存  
            client = new MqttClient(HOST, clientid, new MemoryPersistence());  
            connect();  
        }  
          
        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 PushCallback());  
                   client.connect(options);  
                   topic = client.getTopic(TOPIC);  
            } catch (Exception e) {  
                   e.printStackTrace();  
            }  
        }  
          
        public void publish(MqttMessage message) throws MqttPersistenceException, MqttException{  
            MqttDeliveryToken token = topic.publish(message);  
            token.waitForCompletion();  
            System.out.println(token.isComplete()+"========");  
        }  
      
        public static void main(String[] args) throws MqttException {  
            Server server =  new Server();  
            server.message = new MqttMessage();  
            server.message.setQos(1);  
            server.message.setRetained(true);  
            server.message.setPayload("eeeeeaaaaaawwwwww---".getBytes());  
            server.publish(server.message);  
            System.out.println(server.message.isRetained()+"------ratained状态");  
        }  
    }  

    客户端接收消息

    import java.util.concurrent.Executors;  
    import java.util.concurrent.ScheduledExecutorService;  
    import java.util.concurrent.TimeUnit;  
      
    import org.eclipse.paho.client.mqttv3.MqttClient;  
    import org.eclipse.paho.client.mqttv3.MqttConnectOptions;  
    import org.eclipse.paho.client.mqttv3.MqttException;  
    import org.eclipse.paho.client.mqttv3.MqttSecurityException;  
    import org.eclipse.paho.client.mqttv3.MqttTopic;  
    import org.eclipse.paho.client.mqttv3.internal.MemoryPersistence;  
      
    public class Client {  
      
        public static final String HOST = "tcp://127.0.0.1:1883"; 
    public static final String TOPIC = "主题"; private static final String clientid = "server_clientid";
    private MqttClient client; private MqttConnectOptions options; private String userName = "test"; private String passWord = "test"; private ScheduledExecutorService scheduler; //重新链接 public void startReconnect() { scheduler = Executors.newSingleThreadScheduledExecutor(); scheduler.scheduleAtFixedRate(new Runnable() { public void run() { if (!client.isConnected()) { try { client.connect(options); } catch (MqttSecurityException e) { e.printStackTrace(); } catch (MqttException e) { e.printStackTrace(); } } } }, 0 * 1000, 10 * 1000, TimeUnit.MILLISECONDS); } private void start() { try { // host为主机名,test为clientid即连接MQTT的客户端ID,一般以客户端唯一标识符表示,MemoryPersistence设置clientid的保存形式,默认为以内存保存 client = new MqttClient(HOST, clientid, 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 PushCallback()); MqttTopic topic = client.getTopic(TOPIC); //setWill方法,如果项目中需要知道客户端是否掉线可以调用该方法。设置最终端口的通知消息 options.setWill(topic, "close".getBytes(), 0, true); client.connect(options); //订阅消息 int[] Qos = {1}; String[] topic1 = {TOPIC}; client.subscribe(topic1, Qos); } catch (Exception e) { e.printStackTrace(); } } public void disconnect() { try { client.disconnect(); } catch (MqttException e) { e.printStackTrace(); } } public static void main(String[] args) throws MqttException { Client client = new Client(); client.start(); } }

    Maven依赖

    <dependency>
          <groupId>org.eclipse.paho</groupId>
          <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
          <version>1.1.1</version>
    </dependency>

    原文链接

  • 相关阅读:
    航空公司客户价值分析
    电力窃漏电用户自动识别
    信息论基础
    Xgboost
    直线或曲线拟合
    matplotlib中绘图
    pandas中绘图
    matplotlib图形设置
    子图subplot
    时间序列:时间序列理论、时间序列建模
  • 原文地址:https://www.cnblogs.com/LJing21/p/15840525.html
Copyright © 2020-2023  润新知