springboot整合多个mqtt
前提:实际应用中,项目需要订阅其他服务器的消息,并且把本机消息发布,所以需要连接多个mqtt,实现不同的需求
参考:https://blog.csdn.net/qq_40083897/article/details/117333716
整合大致结构如下:
1. 引入依赖
<!-- mqtt -->
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mqtt</artifactId>
</dependency>
2.连接配置
#MQTT配置信息
mqtt:
# 设备连接
channel1:
username: xxxxxx
password: xxxxxx
host: tcp://192.168.xx.xxx:1883
clientid: device_channel1
timeout: 1000
keepalive: 10
cleanSession: false
#本地连接
channel2:
username: xxxxxx
password: xxxxxx
host: tcp://192.168.xx.xxx:1883
clientid: local_channel2
timeout: 1000
keepalive: 10
cleanSession: false
3.连接配置
@Data
public class MqttConfiguration {
private String host;
private String clientId;
private String userName;
private String password;
private String topic;
private int timeout;
private int keepAlive;
private boolean cleanSession;
}
@Slf4j
@Component
@Configuration
@ConfigurationProperties(MqttConfiguration1.PREFIX)
public class MqttConfiguration1 extends MqttConfiguration{
//指定配置文件application-local.properties中的属性名前缀
public static final String PREFIX = "spring.mqtt.channel1";
}
@Slf4j
@Component
@Configuration
@ConfigurationProperties(MqttConfiguration2.PREFIX)
public class MqttConfiguration2 extends MqttConfiguration{
//指定配置文件application-local.properties中的属性名前缀
public static final String PREFIX = "spring.mqtt.channel2";
}
4.连接管理
@Slf4j
@Component
public class MqttClientConnection {
private MqttClient mqttClient;
/**
* 系统的mqtt客户端id
*/
private String mqttClientId;
/**
* 客户端
*/
public static ConcurrentHashMap<String, MqttClientConnection> mqttClients = new ConcurrentHashMap();
/**
* 客户端connect连接mqtt服务器
*
* @param userName 用户名
* @param passWord 密码
* @param mqttCallback 回调函数
**/
public MqttClient setMqttClient(String host, String clientId, String userName, String passWord, boolean cleanSession, MqttCallback mqttCallback) throws MqttException {
MqttConnectOptions options = mqttConnectOptions(host, clientId, userName, passWord, cleanSession);
if (mqttCallback == null) {
mqttClient.setCallback(new MqttClientCallback(mqttClientId));
} else {
mqttClient.setCallback(mqttCallback);
}
mqttClient.connect(options);
return mqttClient;
}
/**
* MQTT连接参数设置
*/
private MqttConnectOptions mqttConnectOptions(String host, String clientId, String userName, String passWord, boolean cleanSession) throws MqttException {
mqttClient = new MqttClient(host, clientId, new MemoryPersistence());
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(userName);
options.setPassword(passWord.toCharArray());
options.setConnectionTimeout(60);///默认:30
options.setAutomaticReconnect(true);//默认:false
options.setCleanSession(cleanSession);//默认:true
options.setKeepAliveInterval(60);
return options;
}
/**
* 关闭MQTT连接
*/
public void close() throws MqttException {
mqttClient.close();
mqttClient.disconnect();
}
/**
* 向某个主题发布消息 默认qos:1
*
* @param topic:发布的主题
* @param msg:发布的消息
*/
public void pub(String topic, String msg) throws MqttException {
MqttMessage mqttMessage = new MqttMessage();
//mqttMessage.setQos(2);
mqttMessage.setPayload(msg.getBytes());
MqttTopic mqttTopic = mqttClient.getTopic(topic);
MqttDeliveryToken token = mqttTopic.publish(mqttMessage);
token.waitForCompletion();
}
/**
* 向某个主题发布消息
*
* @param topic: 发布的主题
* @param msg: 发布的消息
* @param qos: 消息质量 Qos:0、1、2
*/
public void pub(String topic, String msg, int qos) throws MqttException {
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setQos(qos);
mqttMessage.setPayload(msg.getBytes());
MqttTopic mqttTopic = mqttClient.getTopic(topic);
MqttDeliveryToken token = mqttTopic.publish(mqttMessage);
token.waitForCompletion();
}
/**
* 订阅多个主题 ,此方法默认的的Qos等级为:1
*
* @param topic 主题
*/
public void sub(String[] topic) throws MqttException {
mqttClient.subscribe(topic);
}
/**
* 订阅某一个主题,可携带Qos
*
* @param topic 所要订阅的主题
* @param qos 消息质量:0、1、2
*/
public void sub(String topic, int qos) throws MqttException {
mqttClient.subscribe(topic, qos);
}
public String getMqttClientId() {
return mqttClientId;
}
public void setMqttClientId(String mqttClientId) {
this.mqttClientId = mqttClientId;
}
public MqttClient getMqttClient(){
return mqttClient;
}
}
5.回调函数
@Slf4j
@Component
public class MqttClientCallback implements MqttCallback {
/**
* 系统的mqtt客户端id
*/
private static String mqttClientId;
public MqttClientCallback(){}
public MqttClientCallback(String mqttClientId) {
this.mqttClientId = mqttClientId;
}
/**
* MQTT 断开连接会执行此方法
*/
@Override
public void connectionLost(Throwable throwable) {
log.info("断开了MQTT连接", throwable);
}
/**
* publish发布成功后会执行到这里
*/
@Override
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
log.info("发布消息成功");
}
/**
* subscribe订阅后得到的消息会执行到这里
*/
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
// TODO 此处可以将订阅得到的消息进行业务处理、数据存储
}
6.项目监听
@Slf4j
@Component
public class MqttClientListener implements ApplicationListener<ContextRefreshedEvent> {
@Autowired
private MqttConfiguration1 mqttConfiguration1;
@Autowired
private MqttConfiguration2 mqttConfiguration2;
private volatile AtomicBoolean isInit = new AtomicBoolean(false);
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
//防止重复触发
if (!isInit.compareAndSet(false, true)) {
return;
}
try {
MqttClientConnection mqttClientConnect1 = new MqttClientConnection();
mqttClientConnect1.setMqttClientId(mqttConfiguration2.getClientId());
mqttClientConnect1.setMqttClient(mqttConfiguration2.getHost(), mqttConfiguration2.getClientId(), mqttConfiguration2.getUserName(), mqttConfiguration2.getPassword(), false, new MqttClientCallback(mqttConfiguration2.getClientId()));
MqttClientConnection.mqttClients.put(mqttConfiguration2.getClientId(), mqttClientConnect1);
log.info("{}--连接成功!!订阅主题--{}", mqttConfiguration2.getClientId(), mqttConfiguration2.getTopic(), mqttConfiguration2.getKeepAlive(), mqttConfiguration2.getTimeout());
}catch (Exception e) {
log.error("本地mqtt连接失败", e);
}
try {
MqttClientConnection mqttClientConnect = new MqttClientConnection();
mqttClientConnect.setMqttClientId(mqttConfiguration1.getClientId());
mqttClientConnect.setMqttClient(mqttConfiguration1.getHost(), mqttConfiguration1.getClientId(), mqttConfiguration1.getUserName(), mqttConfiguration1.getPassword(), false, new MqttClientCallback(mqttConfiguration1.getClientId()));
MqttClientConnection.mqttClients.put(mqttConfiguration1.getClientId(), mqttClientConnect);
log.info("{}--连接成功!!订阅主题--{}", mqttConfiguration1.getClientId(), mqttConfiguration1.getTopic(),mqttConfiguration1.getKeepAlive(),mqttConfiguration1.getTimeout());
}catch (Exception e) {
log.error("连接设备mqtt失败", e);
}
}
}
7.实际使用
1、订阅
MqttClientConnection mqttClientConnection = MqttClientConnection.mqttClients.get("device_channel1");
MqttClient client = mqttClientConnection.getMqttClient();
try {
// 订阅返回值消息
client.subscribe("mqtt",0, new IMqttMessageListener() {
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
String data = new String(message.getPayload());
}
});
} catch (MqttException e) {
log.error("mqtt订阅报错",e);
}
2、发布
MqttClientConnection mqttClient= MqttClientConnection.mqttClients.get("local_channel2");
try {
mqttClient.pub("topic",JSON.toJSONString(data));
} catch (MqttException e) {
log.error("mqtt推送消息异常",e);
}
由此完成了springboot集合多个mqtt。
但是实际应用中又发现,订阅方丢失了数据,一直没有接收到新数据,但是重新订阅又可以,所以定位问题到mqtt进行了重连并且在连接配置中设置了options.setAutomaticReconnect(true);mqtt会断线自动重新连接,但是之前订阅的topic并没有接着接收信息,如果想继续接收信息,必须重新订阅这些topic。
参考:https://blog.csdn.net/paladinzh/article/details/88053530
对回调函数 做了以下调整:
@Slf4j
@Component
public class MqttClientCallback implements MqttCallback,MqttCallbackExtended {
/**
* 系统的mqtt客户端id
*/
private static String mqttClientId;
@Autowired
private MqttConfiguration1 mqttConfiguration1;
@Autowired
private MqttConfiguration2 mqttConfiguration2;
public MqttClientCallback(){}
public MqttClientCallback(String mqttClientId) {
this.mqttClientId = mqttClientId;
}
@Autowired
private IAlarmConfigService alarmConfigService;
@Autowired
private DeviceMqttSub deviceMqttSub;
public static MqttClientCallback mqttClientCallback ;
//通过@PostConstruct实现初始化bean之前进行的操作
@PostConstruct
public void init() {
log.info("初始化");
mqttClientCallback = this;
mqttClientCallback.alarmConfigService = this.alarmConfigService;
mqttClientCallback.deviceMqttSub = this.deviceMqttSub;
mqttClientCallback.mqttConfiguration1 = this.mqttConfiguration1;
mqttClientCallback.mqttConfiguration2 = this.mqttConfiguration2;
}
/**
* MQTT 断开连接会执行此方法
*/
@Override
public void connectionLost(Throwable throwable) {
log.info("断开了MQTT连接", throwable);
}
/**
* publish发布成功后会执行到这里
*/
@Override
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
log.info("发布消息成功");
}
/**
* subscribe订阅后得到的消息会执行到这里
*/
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
// TODO 此处可以将订阅得到的消息进行业务处理、数据存储
}
@Override
public void connectComplete(boolean b, String s) {
if (!b) {
log.info("第一次连接成功");
}else{
// 判断是哪个mqtt重启需要重新订阅
if (s.equals(mqttClientCallback.mqttConfiguration1.getHost())) {
log.info("重新订阅所有设备信息");
List<AlarmConfigVo> alarmConfigVos = mqttClientCallback.alarmConfigService.searchList(new AlarmConfig());
for (int i = 0; i < alarmConfigVos.size(); i++) {
List<AlarmRuleConfigVo> alarmRuleConfigs = alarmConfigVos.get(i).getAlarmRuleConfigs();
for (int a = 0; a < alarmRuleConfigs.size(); a++) {
if (alarmRuleConfigs.get(a).getStatus() == 1) {
List<AlarmConfigDevice> devices = alarmRuleConfigs.get(a).getDevices();
for (AlarmConfigDevice alarmConfigDevice : devices) {
mqttClientCallback.deviceMqttSub.mattDevice(alarmConfigDevice);
}
}
}
}
}
}
}
}