🧑🎓 个人主页:SilenceLamb
📖 本章内容:【MQTT实现消息的发布-订阅】
🌳Gitee仓库地址:👉🏽MQTT实现消息的发布订阅
- 实现MQTT协议需要客户端和服务器端通讯完成
- 在通讯过程中, MQTT协议中有三种身份: 发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)
- 其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者
🌳MQQT官方文档: 👉🏽SpringBoot整合MQQT
引入依赖
<!--mqtt-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</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>
配置信息
- 👉🏽可以不做任何更改
mqqt:
username: michale
password: michale # 密码
hostUrl: tcp://broker.emqx.io:1883 # tcp://ip:端口
clientId: clientId # 客户端id
defaultTopic: topic # 订阅主题
timeout: 1000 # 超时时间 (单位:秒)
keepalive: 60 # 心跳 (单位:秒)
enabled: false # 是否使能mqtt功能
- 如果客户端订阅主题「topic/test/player1/#」,它会收到使用下列主题名发布的消息
「topic/test/player1」
「topic/test/player1/ranking」
「topic/test/player1/score/wimbledon」
- 👉🏽读取配置信息
/**
* @Author Michale
* @CreateDate 2022/9/4
* @Describe 读取MQQT配置信息
*/
@Data
@Component
@ConfigurationProperties(prefix = "mqqt")
public class MQQTProperties {
@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("密码")
private String password;
@ApiModelProperty("地址")
private String hostUrl;
@ApiModelProperty("客户端id")
private String clientId;
@ApiModelProperty("订阅主题")
private String defaultTopic;
@ApiModelProperty("超时时间")
private int timeout;
@ApiModelProperty("心跳")
private int keepalive;
@ApiModelProperty("MQQT开关")
private boolean enabled;
}
MQQT配置类
- 👉🏽 创建MqttPahoClientFactory
- 👉🏽 设置MQTT Broker连接属性
@Bean
public MqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(mqqtProperties.getUsername());
options.setPassword(mqqtProperties.getPassword().toCharArray());
options.setKeepAliveInterval(mqqtProperties.getKeepalive());
options.setAutomaticReconnect(true);
options.setConnectionTimeout(mqqtProperties.getTimeout());
options.setMaxInflight(1000000);
//多个服务器地址时处理
options.setServerURIs(mqqtProperties.getHostUrl().split(","));
factory.setConnectionOptions(options);
return factory;
}
- 👉🏽 配置订阅的topic --支持通配符
@Bean
public MessageProducer inbound() {
//clientId每个连接必须唯一,否则,两个相同的clientId相互挤掉线
String serverIdStr = mqqtProperties.getClientId() + IdUtils.fastUUID();
MqttPahoMessageDrivenChannelAdapter adapter =
new MqttPahoMessageDrivenChannelAdapter(serverIdStr, mqttClientFactory(), mqqtProperties.getDefaultTopic());
adapter.setConverter(new DefaultPahoMessageConverter());
adapter.setCompletionTimeout(mqqtProperties.getTimeout());
adapter.setQos(0);
adapter.setOutputChannel(mqttInputChannel());
return adapter;
}
- 👉🏽 发送通道配置默认主题
@Bean
@ServiceActivator(inputChannel = "mqttOutboundChannel")
public MessageHandler mqttOutbound() {
//clientId每个连接必须唯一,否则,两个相同的clientId相互挤掉线
String clientIdStr = mqqtProperties.getClientId() + new SecureRandom().nextInt(10);
MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler(clientIdStr, mqttClientFactory());
//async如果为true,则调用方不会阻塞。而是在发送消息时等待传递确认。默认值为false(发送将阻塞,直到确认发送)
messageHandler.setAsync(true);
messageHandler.setAsyncEvents(true);
messageHandler.setDefaultTopic(mqqtProperties.getDefaultTopic());
messageHandler.setDefaultQos(0);
return messageHandler;
}
- 👉🏽 通过通道获取订阅的数据 (可不选)
@Bean
@ServiceActivator(inputChannel = "mqttInputChannel")
public MessageHandler handler() {
return new MessageHandler() {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
MqttDeliveryToken token = new MqttDeliveryToken();
String payload = message.getPayload().toString();
String topic = message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC).toString();
//处理订阅到的所有的数据
System.out.println(payload);
}
};
}
- 👉🏽创建两个通道
/**
* 发送通道
*/
@Bean
public MessageChannel mqttOutboundChannel() {
return new DirectChannel();
}
/**
* 接收通道
*/
@Bean
public MessageChannel mqttInputChannel() {
return new DirectChannel();
}
MQTT发送网关
- 👉🏽 建议直接复制
/**
* @Author Michale
* @CreateDate 2022/9/16
* @Describe 发送消息
*/
@Component
@MessagingGateway(defaultRequestChannel = MQTT_OUTBOUND_CHANNEL)
public interface MqqtSend {
/**
* 定义重载方法,用于消息发送
*
* @param payload 消息报文
*/
void send(String payload);
/**
* 指定topic进行消息发送
*
* @param topic 主题
* @param payload 消息报文
*/
void send(@Header(MqttHeaders.TOPIC) String topic, String payload);
/**
* 指定topic和通道 进行消息发送
*
* @param topic 主题
* @param qos 对消息处理的几种机制。
* 0 表示的是订阅者没收到消息不会再次发送,消息会丢失。
* 1 表示的是会尝试重试,一直到接收到消息,但这种情况可能导致订阅者收到多次重复消息。
* 2 多了一次去重的动作,确保订阅者收到的消息有一次。
* @param payload 消息报文
*/
void send(@Header(MqttHeaders.TOPIC) String topic, @Header(MqttHeaders.QOS) int qos, String payload);
}
- 引入
import org.springframework.messaging.handler.annotation.Header;
接收到的消息
/**
* @Author Michale
* @CreateDate 2022/9/16
* @Describe 接收到的数据
*/
@Slf4j
@Component
public class MqqtMessage implements MessageHandler {
/**
* Handle the given message.
*
* @param message the message to be handled
* @throws MessagingException if the handler failed to process the message
*/
@Override
@ServiceActivator(inputChannel = MQTT_INPUT_CHANNEL)
public void handleMessage(Message<?> message) throws MessagingException {
String topic = message.getHeaders().get(MQTT_RECEIVED_TOPIC).toString();
String payload = message.getPayload().toString();
log.info("订阅的主题:{} 发送的内容:{}", topic, payload);
checkTopic(topic, payload);
}
/**
* 模拟硬件设备
*
* @param topic 订阅的主题
* @param payload 消息报文
*/
private void checkTopic(String topic, String payload) {
if (StringUtils.inStringIgnoreCase(topic, "michale/furniture/led")) {
extracted(payload, "客厅灯已打开", "客厅灯已关闭");
} else if ((StringUtils.inStringIgnoreCase(topic, "michale/furniture/tv"))) {
extracted(payload, "电视已打开", "电视已关闭");
}
}
/**
* 打印日志
*
* @param payload 消息报文
* @param megTrue 正确信息
* @param msgFalse 错误信息
*/
private void extracted(String payload, String megTrue, String msgFalse) {
if (StringUtils.inStringIgnoreCase(payload, "1")) {
log.info(megTrue);
} else {
log.info(msgFalse);
}
}
}
演示MQQT
/**
* @Author Michale
* @CreateDate 2022/9/16
* @Describe Mqqt消息体
*/
@Data
public class MqqtVo {
@ApiModelProperty("订阅的主题")
public String topic ;
@ApiModelProperty("发送的内容")
public String payload ;
}
/**
* 对接硬件设备
*
* @return
*/
@Anonymous
@PostMapping("/michale/led")
public AjaxResult openLed(@RequestBody MqqtVo mqqtvo ) {
mqqtSend.send(mqqtvo.getTopic(), mqqtvo.getPayload());
return new AjaxResult().put("led_status", "open");
}
- 👉🏽发送请求
{
"payload": "1",
"topic": "michale/furniture/led"
}
- 👉🏽MQQT客户端发送
📢💨如果文章对你有帮助【关注👍点赞❤️收藏⭐】