关于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>

原文链接

posted @ 2022-01-27 17:13  21karat  阅读(1139)  评论(0编辑  收藏  举报