前言:
- 本位为 Idea+maven+spring-cloud项目搭建系列,maven项目的创建可以参考:
https://blog.csdn.net/l123lgx/article/details/121467823 - 本文使用了nacos 作为微服务的注册与发现,nacos 阿里云服务器的安装可以参考:https://blog.csdn.net/l123lgx/article/details/121421431
nacos 服务端的配置和使用可以参考:
https://blog.csdn.net/l123lgx/article/details/121491529 - 本文在安装Mqtt(eclipse-mosquitto)服务基础上进行的扩展,阿里云轻量服务器–Docker–Mqtt(eclipse-mosquitto)安装可以参考:https://blog.csdn.net/l123lgx/article/details/121519938
1 mqtt 简介:
Mqtt 是一中传输协议,底层依靠的是tcp ,与http 和udp 相同度都是数据传输协议;与http 不同的是他是一个客户端-服务端架构的发布/订阅模式的消息传输协议;最初目标是在昂贵的、不可靠的通信线路上尽可能地实现最小和最有效的数据传输,也即他是为受限的设备提供的数据传输协议;它的设计思想是轻巧、开放、简单、规范,易于实现。这些特点使得它对很多场景来说都是很好的选择,特别是对于受限的环境如机器与机器的通信(M2M)以及物联网环境(IoT),因为在物联网上大多数的设备由于带宽和内存的限制,所以在物联网中使用Mqtt 协议进行数据传输无疑是最加的选择;
2 springboot 整合mqtt:
2.1 引入jar:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mqtt</artifactId>
</dependency>
2.2 nacos 添加配置:
spring:
mqtt:
send:
#完成超时时间
completionTimeout: 3000
#通过mqtt发送消息验证所需用户名
username: mmy
#通过mqtt发送消息验证所需密码
password: mmy123
#连接的mqtt地址
url: tcp://192.168.1.101:1883
#客户端id
clientId: clint1
#推送主题 后面跟着#是监控下面所有的话题
topic: topic
#topic: my-test
# 会话心跳时间
keepAliveInterval: 20
# 设置连接超时时间
connectionTimeout: 3000
# 要消费的topic配置
receive:
topic: sharjeck/ai/test/out,test
2.3 java配置和使用类
MqttConfig:
package com.mmy.springmmyservice.config;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.core.MessageProducer;
import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;
import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
import org.springframework.messaging.MessageChannel;
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
import org.springframework.messaging.MessageHandler;
/**
* @Description TODO
* @Date 2022/12/20 10:07
* @Author lgx
* @Version 1.0
*/
@Configuration
public class MqttConfig {
private static final byte[] WILL_DATA;
static {
WILL_DATA = "offline".getBytes();
}
/**
* mqtt订阅者使用信道名称
*/
public static final String CHANNEL_NAME_IN = "mqttInboundChannel";
/**
* mqtt发布者信道名称
*/
public static final String CHANNEL_NAME_OUT = "mqttOutboundChannel";
/**
* mqtt发送者用户名
*/
@Value("${spring.mqtt.send.username}")
private String username;
/**
* mqtt发送者密码
*/
@Value("${spring.mqtt.send.password}")
private String password;
/**
* mqtt发送者url
*/
@Value("${spring.mqtt.send.url}")
private String hostUrl;
/**
* mqtt发送者客户端id
*/
@Value("${spring.mqtt.send.clientId}")
private String clientId;
/**
* mqtt发送者主题
*/
@Value("${spring.mqtt.send.topic}")
private String msgTopic;
/**
* mqtt发送者主题
*/
@Value("${spring.mqtt.receive.topic}")
private String msgReceiveTopic;
/**
* mqtt发送者超时时间
*/
@Value("${spring.mqtt.send.completionTimeout}")
private int completionTimeout;
/**
* @author liujianfu
* @description
*/
@Value("${spring.mqtt.send.keepAliveInterval}")
private int keepAliveInterval;
/**
* @author liujianfu
* @description
*/
@Value("${spring.mqtt.send.connectionTimeout}")
private int connectionTimeout;
@Autowired
private MqttCallbackHandler mqttCallbackHandler;
/**
* @param
* @return org.eclipse.paho.client.mqttv3.MqttConnectOptions
* @author liujianfu
* @description 新建MqttConnectionOptionsBean MQTT连接器选项
* @date 2021/8/17 10:34
*/
@Bean
public MqttConnectOptions getSenderMqttConnectOptions() {
MqttConnectOptions options = new MqttConnectOptions();
// 设置连接的用户名
if (!username.trim().equals("")) {
//将用户名去掉前后空格
options.setUserName(username);
}
// 设置连接的密码
options.setPassword(password.toCharArray());
// 转化连接的url地址
String[] uris = {hostUrl};
// 设置连接的地址
options.setServerURIs(uris);
// 设置超时时间 单位为秒
options.setConnectionTimeout(completionTimeout);
// 设置会话心跳时间 单位为秒 服务器会每隔1.5*20秒的时间向客户端发送心跳判断客户端是否在线
// 但这个方法并没有重连的机制
options.setKeepAliveInterval(keepAliveInterval);
// 设置“遗嘱”消息的话题,若客户端与服务器之间的连接意外中断,服务器将发布客户端的“遗嘱”消息。
//设置超时时间
options.setConnectionTimeout(connectionTimeout);
options.setCleanSession(true);
options.setAutomaticReconnect(true);
return options;
}
/**
* 创建MqttPathClientFactoryBean
*/
@Bean
public MqttPahoClientFactory senderMqttClientFactory() {
//创建mqtt客户端工厂
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
//设置mqtt的连接设置
factory.setConnectionOptions(getSenderMqttConnectOptions());
return factory;
}
/**
* 发布者-MQTT信息通道(生产者)
*/
@Bean(name = CHANNEL_NAME_OUT)
public MessageChannel mqttOutboundChannel() {
return new DirectChannel();
}
/**
* 发布者-MQTT消息处理器(生产者) 将channel绑定到MqttClientFactory上
*
* @return {@link org.springframework.messaging.MessageHandler}
*/
@Bean
@ServiceActivator(inputChannel = CHANNEL_NAME_OUT)
public MessageHandler mqttOutbound() {
//创建消息处理器
MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler(
clientId + "_pub",
senderMqttClientFactory());
//设置消息处理类型为异步
messageHandler.setAsync(true);
//设置消息的默认主题
messageHandler.setDefaultTopic(msgTopic);
messageHandler.setDefaultRetained(false);
//1.重新连接MQTT服务时,不需要接收该主题最新消息,设置retained为false;
//2.重新连接MQTT服务时,需要接收该主题最新消息,设置retained为true;
return messageHandler;
}
/************ 消费者,订阅者的消费信息 *****/
/**
* MQTT信息通道(消费者)
*/
@Bean(name = CHANNEL_NAME_IN)
public MessageChannel mqttInboundChannel() {
return new DirectChannel();
}
/**
* MQTT消息订阅绑定(消费者)
*/
@Bean
public MessageProducer inbound() {
// System.out.println("topics:" + msgTopic);
// 可以同时消费(订阅)多个Topic
MqttPahoMessageDrivenChannelAdapter adapter =
new MqttPahoMessageDrivenChannelAdapter(
clientId + "_sub", senderMqttClientFactory(), msgReceiveTopic.split(","));
adapter.setCompletionTimeout(5000);
adapter.setConverter(new DefaultPahoMessageConverter());
adapter.setQos(0);
// 设置订阅通道
adapter.setOutputChannel(mqttInboundChannel());
return adapter;
}
/**
* MQTT消息处理器(消费者)
*/
@Bean
@ServiceActivator(inputChannel = CHANNEL_NAME_IN)
public MessageHandler handler() {
return message -> {
String topic = message.getHeaders().get("mqtt_receivedTopic").toString();
String payload = message.getPayload().toString();
mqttCallbackHandler.handle(topic, payload);
};
}
}
MqttCallbackHandler:消费topic 处理类:
@Service
public class MqttCallbackHandler {
public void handle(String topic, String payload) {
// 根据topic分别进行消息处理。
System.out.println("MqttCallbackHandle:" + topic + "|" + payload);
}
}
发送消息接口MqSendMessageGateWay:
package com.mmy.springmmyservice.config;
import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.stereotype.Component;
import org.springframework.messaging.handler.annotation.Header;
/**
* @Description TODO
* @Date 2022/12/20 10:15
* @Author lgx
* @Version 1.0
*/
@Component
@MessagingGateway(defaultRequestChannel = MqttConfig.CHANNEL_NAME_OUT)
public interface MqSendMessageGateWay {
/**
* 默认的消息机制
*
* @param data
*/
void sendToMqtt(String data);
/**
* 发送消息 向mqtt指定topic发送消息
*
* @param topic
* @param payload
*/
void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, String payload);
/**
* 发送消息 向mqtt指定topic发送消息
*
* @param topic
* @param qos
* @param payload
*/
void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, @Header(MqttHeaders.QOS) int qos, String payload);
}
控制器发送消息测试MqttController:
package com.mmy.springmmyservice.modules.pagerecord.controller;
import com.alibaba.fastjson2.JSONObject;
import com.mmy.springmmyservice.common.CommonResponseBody;
import com.mmy.springmmyservice.config.MqSendMessageGateWay;
import com.mmy.springmmyservice.modules.pagerecord.dto.MqttSendDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
/**
* @Description TODO
* @Date 2022/12/20 10:18
* @Author lgx
* @Version 1.0
*/
@RestController
@CommonResponseBody
public class MqttController {
@Autowired
private MqSendMessageGateWay mqSendMessageGateWay;
@RequestMapping("/send")
private ResponseEntity<String> send() {
String data = "我是springboot发送的数据";
mqSendMessageGateWay.sendToMqtt(data);
// return R.ok("OK");
return new ResponseEntity<>("OK", HttpStatus.OK);
}
/**
* 动态增加主题
*
* @param
* @param
*/
@RequestMapping("/sendToTopic")
private ResponseEntity<String> sendToTopic() {
String topic = "sharjeck/ai/test/out";
String data = "这是出的主题";
mqSendMessageGateWay.sendToMqtt(topic, data);
return new ResponseEntity<>("OK", HttpStatus.OK);
}
/**
* 动态增加主题
*
* @param
* @param
*/
@PostMapping("/sendToTopic")
private String sendToTopic(@RequestBody MqttSendDto reqDto) {
mqSendMessageGateWay.sendToMqtt(reqDto.getTopic(), JSONObject.toJSONString(reqDto.getJsonStr()));
return "OK";
}
}
3 总结:
3.1 mqtt 和http ,tcp,udp,一样只是传输协议使用sdk 即可,并不像kafka ,rabbitmq 一样是消息组件;
3.2 场景不同 kafka ,rabbitmq 面向服务端的消息引擎,主要用于服务组件之间的解耦、异步通知、削峰填谷等,服务器规模较小(极少企业服务器规模过万),但需要大量的消息处理,吞吐量要求高;mqtt 面向移动端场景,移动端场景一般都具备海量设备,单设备数据较少的特点。因此,微消息队列 MQTT 版适用于拥有大量在线客户端(很多企业设备端过万,甚至上百万),但每个客户端消息较少的场景;
3.3 对于部署在服务器上的应用,推荐使用消息队列 RocketMQ 版接入;对于部署在移动终端、App 或浏览器页面等平台上的应用,推荐使用微消息队列 MQTT 版接入。
参考:
1 MQTT Support;
2 springBoot整合mosquitto MQTT服务接收和发送;
3 微消息队列MQTT与RocketMQ/Kafka/RabbitMQ区别;