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