1.开篇聊聊,本人之前从事通信软件开发,对高并发和限流等有一定的实战,不过最近做物联网项目,这里聊聊怎么用mqtt进行双向通信,后面大家对通信开发有兴趣的也可以关注下本人博客!!!
进入主题:
目前市面上有几种mqtt的开源项目,本人觉得emqtt这款开源的项目好用,是EMQTT,
Erlang MQTT消息服务器简称EMQTT。
EMQTT是采用Erlang语言开发,全面支持MQTT V3.1.1协议,支持集群和大规模连接的开源MQTT消息服务器。
EMQTT致力于发布一个基于Erlang/OTP语言平台,企业级稳定可靠,完全开源免费,可集群支持大规模物联网、移动互联网连接的MQTT消息服务器。
本人使用dcoker进行集成安装的在阿里云上面的:
如何安装自行百度!!!
------------------------------》》》》》
下面介绍下emqtt的视图化工具:Dashboard
2.好了,一些环境工作完后就进入到代码阶段了
进行borker,心跳等配置,springboot中的application.properties配置
sprig.mqtt.broker=安装的borker的ip+端口
spring.mqtt.clientId=xxxxx ------>>客户端 ,每一个borker上面的客户端id是唯一的
spring.mqtt.username=xxxxx
spring.mqtt.password=xxxxxx
spring.mqtt.timeout=2000
spring.mqtt.KeepAlive=20
spring.mqtt.topics=xxxx ------->>主题
spring.mqtt.qos=1 ------>>心跳包级别
(上面对这些参数不明白的,可自行百度)
1.进行mqtt项目启动:
-------------------------------------------------------------------
/**
* Title: ApplicationRunnerImpl
* Description:项目启动后执行mqtt
*
* @author YFW
* @version V1.0
* @date 2020-03-06
*/
@Component
@Slf4j
public class MqttApplicationRunner implements ApplicationRunner {
@Autowired
private MqttConfiguration mqttConfiguration;
@Override
public void run(ApplicationArguments args) throws Exception {
if (log.isInfoEnabled()){
log.info("===============>>>Mqtt is run starting:<<==================");
}
MqttPushClient mqttPushClient = new MqttPushClient();
mqttPushClient.connect(mqttConfiguration);
}
public static void main(String[] args) {
System.out.println(MD5Utils.getMd5("abc123456"));
}
}
------------------------------------------------------------------------------------------------------
2.进行MqttConfiguration 的配置;
3.MqttBean的配置:
--------------------------------------------------------
/**
* Title: Mqttbean
* Description: TODO
*
* @author YFW
* @version V1.0
* @date 2020-03-06
*/
@Component
public class Mqttbean {
@Autowired
private MqttConfiguration mqttConfiguration;
@Bean("mqttPushClient")
public MqttPushClient getMqttPushClient() {
MqttPushClient mqttPushClient = new MqttPushClient();
return mqttPushClient;
}
}
-----------------------------------------------------------------------------------
4.MqttClient客户端代码段:
--------------------------------------------------------------
/**
* Title: MqttPushClient
* Description: TODO
*
* @author PJ-YFW
* @version V1.0
* @date 2020-03-06
*/
@Slf4j
public class MqttPushClient {
private static MqttClient client;
public static MqttClient getClient() {
return client;
}
public static void setClient(MqttClient client) {
MqttPushClient.client = client;
}
private MqttConnectOptions getOption(String userName, String password, int outTime, int KeepAlive) {
//MQTT连接设置
MqttConnectOptions option = new MqttConnectOptions();
//设置是否清空session,false表示服务器会保留客户端的连接记录,true表示每次连接到服务器都以新的身份连接
option.setCleanSession(false);
//设置连接的用户名
option.setUserName(userName);
//设置连接的密码
option.setPassword(password.toCharArray());
//设置超时时间 单位为秒
option.setConnectionTimeout(outTime);
//设置会话心跳时间 单位为秒 服务器会每隔(1.5*keepTime)秒的时间向客户端发送个消息判断客户端是否在线,但这个方法并没有重连的机制
option.setKeepAliveInterval(KeepAlive);
//setWill方法,如果项目中需要知道客户端是否掉线可以调用该方法。设置最终端口的通知消息
//option.setWill(topic, "close".getBytes(), 2, true);
option.setMaxInflight(1000);
return option;
}
/**
* 连接
*/
public void connect(MqttConfiguration mqttConfiguration) {
MqttClient client;
try {
client = new MqttClient(mqttConfiguration.getHost(), mqttConfiguration.getClientId(), new MemoryPersistence());
MqttConnectOptions options = getOption(mqttConfiguration.getUsername(), mqttConfiguration.getPassword(),
mqttConfiguration.getTimeout(), mqttConfiguration.getKeepAlive());
MqttPushClient.setClient(client);
try {
client.setCallback(new PushCallback(this, mqttConfiguration));
if (!client.isConnected()) {
client.connect(options);
log.info("================>>>MQTT连接成功<<======================");
//订阅主题
subscribe(mqttConfiguration.getTopic(), mqttConfiguration.getQos());
} else {//这里的逻辑是如果连接不成功就重新连接
client.disconnect();
client.connect(options);
log.info("===================>>>MQTT断连成功<<<======================");
}
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 断线重连
*
* @throws Exception
*/
public Boolean reConnect() throws Exception {
Boolean isConnected = false;
if (null != client) {
client.connect();
if (client.isConnected()) {
isConnected = true;
}
}
return isConnected;
}
/**
* 发布,默认qos为0,非持久化
*
* @param topic
* @param pushMessage
*/
public void publish(String topic, String pushMessage) {
publish(0, false, topic, pushMessage);
}
/**
* 发布
*
* @param qos
* @param retained
* @param topic
* @param pushMessage
*/
public void publish(int qos, boolean retained, String topic, String pushMessage) {
MqttMessage message = new MqttMessage();
message.setQos(qos);
message.setRetained(retained);
message.setPayload(pushMessage.getBytes());
MqttTopic mTopic = MqttPushClient.getClient().getTopic(topic);
if (null == mTopic) {
log.error("===============>>>MQTT topic 不存在<<=======================");
}
MqttDeliveryToken token;
try {
token = mTopic.publish(message);
token.waitForCompletion();
} catch (MqttPersistenceException e) {
e.printStackTrace();
} catch (MqttException e) {
e.printStackTrace();
}
}
/**
* 发布消息的服务质量(推荐为:2-确保消息到达一次。0-至多一次到达;1-至少一次到达,可能重复),
* retained 默认:false-非持久化(是指一条消息消费完,就会被删除;持久化,消费完,还会保存在服务器中,当新的订阅者出现,继续给新订阅者消费)
*
* @param topic
* @param pushMessage
*/
public void publish(int qos, String topic, String pushMessage) {
publish(qos, false, topic, pushMessage);
}
/**
* 订阅某个主题,qos默认为0
* @param topic
*/
// public void subscribe(String[] topic){
// subscribe(topic,);
// }
/**
* 订阅某个主题
*
* @param topic
* @param qos
*/
public void subscribe(String[] topic, int[] qos) {
try {
MqttPushClient.getClient().unsubscribe(topic);
MqttPushClient.getClient().subscribe(topic, qos);
} catch (MqttException e) {
e.printStackTrace();
}
}
}
------------------------------------------------------------------------------------------------
5.主题发布代码段:
---------------------------------------------------------------------------------
/**
* Title: MqttSender
* Description: TODO
*
* @author PJ-YFW
* @version V1.0
* @date 2020-03-06
*/
@Component(value = "mqttSender")
@Slf4j
public class MqttSender
{
@Async
public void send(String queueName, String msg) {
log.info("=====================>>>>发送主题"+queueName);
publish(2,queueName, msg);
}
/**
* 发布,默认qos为0,非持久化
* @param topic
* @param pushMessage
*/
public void publish(String topic,String pushMessage){
publish(1, false, topic, pushMessage);
}
/**
* 发布
* @param qos
* @param retained
* @param topic
* @param pushMessage
*/
public void publish(int qos,boolean retained,String topic,String pushMessage){
MqttMessage message = new MqttMessage();
message.setQos(qos);
message.setRetained(retained);
message.setPayload(pushMessage.getBytes());
MqttTopic mTopic = MqttPushClient.getClient().getTopic(topic);
if(null == mTopic){
log.error("===================>>>MQTT topic 不存在<<=================");
}
MqttDeliveryToken token;
try {
token = mTopic.publish(message);
token.waitForCompletion();
} catch (MqttPersistenceException e) {
log.error("============>>>publish fail",e);
e.printStackTrace();
} catch (MqttException e) {
e.printStackTrace();
}
}
/**
* 发布消息的服务质量(推荐为:2-确保消息到达一次。0-至多一次到达;1-至少一次到达,可能重复),
* retained 默认:false-非持久化(是指一条消息消费完,就会被删除;持久化,消费完,还会保存在服务器中,当新的订阅者出现,继续给新订阅者消费)
* @param topic
* @param pushMessage
*/
public void publish(int qos, String topic, String pushMessage){
publish(qos, false, topic, pushMessage);
}
}
-----------------------------------------------------------------------------------------------------
6.进行双向通信的时候,监听订阅的客户端和主题是否处于连接状态:
-----------------------------------------------------------------------------------------
/**
* Title: PushCallback
* Description: TODO
*
* @author PJ-YFW
* @version V1.0
* @date 2020-03-06
*/
@Slf4j
public class PushCallback implements MqttCallback {
private MqttPushClient client;
private MqttConfiguration mqttConfiguration;
public PushCallback(MqttPushClient client,MqttConfiguration mqttConfiguration) {
this.client = client;
this.mqttConfiguration = mqttConfiguration;
}
/* public PushCallback(MqttPushClient client,RedisTemplate redisUtils,MqttPtsService mqttStpService) {
this.client = client;
this.redisDao = redisUtils;
this.mqttStpService=mqttStpService;
}*/
@Override
public void connectionLost(Throwable cause) {
/** 连接丢失后,一般在这里面进行重连 **/
if(client != null) {
while (true) {
try {
log.info("==============》》》[MQTT] 连接断开,30S之后尝试重连...");
Thread.sleep(30000);
MqttPushClient mqttPushClient = new MqttPushClient();
mqttPushClient.connect(mqttConfiguration);
if(MqttPushClient.getClient().isConnected()){
log.info("=============>>重连成功");
}
break;
} catch (Exception e) {
log.error("=============>>>[MQTT] 连接断开,重连失败!<<=============");
continue;
}
}
}
log.info(cause.getMessage());
}
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
//publish后会执行到这里
log.info("pushComplete==============>>>" + token.isComplete());
}
/**
* 监听对应的主题消息
* @param topic
* @param message
* @throws Exception
*/
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
// subscribe后得到的消息会执行到这里面
log.info("============》》接收消息主题 : " + topic);
log.info("============》》接收消息Qos : " + message.getQos());
log.info("============》》接收消息内容 : " + new String(message.getPayload()));
try {
// JSONObject jsonObject = JSON.parseObject(new String(message.getPayload(), "UTF-8"));
// MqttDataEntity mqttDataEntity=(MqttDataEntity)JSONObject.toJavaObject(jsonObject, MqttDataEntity.class);
// getMqttDataEntity(jsonObject);
// mqttStpService.handleReceiveMsg(mqttDataEntity);
} catch (Exception e) {
e.printStackTrace();
}
}
}
-------------------------------------------------------------------------------------------------
好了,进过上面的6个步骤,基本一个简单版的mqtt双向通信完成了,下面用一个测试类测试下:
----------------------------------------------------------------------------------
@RestController
public class MqSubController
{
@Autowired
private MqttSender mqttSender;
@Autowired
private MqttPushClient mqttPushClient;
@RequestMapping("/mqttop")
public String mqttop(){
String TOPIC1="test_topic1";
String TOPIC2="test_topic2";
String TOPIC3="test_topic3";
String TOPIC4="test_topic4";
int Qos1=1;
int Qos2=1;
int Qos3=1;
int Qos4=1;
String[] topics={TOPIC1,TOPIC2,TOPIC3,TOPIC4};
int[] qos={Qos1,Qos2,Qos3,Qos4};
mqttPushClient.subscribe(topics,qos);
return "订阅主题";
}
@RequestMapping("/mqttest")
public String mqtest(){
String TOPIC1="test_topic1";
String TOPIC2="test_topic2";
String TOPIC3="test_topic3";
String TOPIC4="test_topic4";
String[] topics={TOPIC1,TOPIC2,TOPIC3,TOPIC4};
mqttSender.send(topics[new Random().nextInt(5)], JSONObject.toJSONString("测试消息发送"));
return "发送成功吗!!!!!!!!";
}
}
--------------------------------------------------------------
总结:上面的代码就是本人在物联网项目中用的mqtt进行长连接进行消息的推送等功能,在当今的物联网开发中经常用用到mqtt进行双向通信的,介绍一个官方的学习地址:https://docs.emqx.io/tutorial/latest/en/
有兴趣的朋友可以上去看下哦,也欢迎同行互联交流技术!!!!!