一、MQTT是什么
MQTT是一个客户端服务端架构的发布/订阅模式的消息传输协议。它的设计思想是轻巧、开放、简单、规范,易于实现。这些特点使得它对很多场景来说都是很好的选择,特别是对于受限的环境如机器与机器的通信(M2M)以及物联网环境(IoT)。
二、MQTT的主要通讯方式
MQTT的主要通讯方式是发布与订阅,客户端可以作为消息的发布者,也可以作为消息的订阅者。当发布者对某一主题进行消息的发布后,订阅了同一主题的订阅者便可以接收到发布的信息。在 MQTT 中默认是广播的,也就是说订阅了相同 topic 的订阅者都能收到发布者发送的消息。
三、springboot整合Mqtt
本文主要通过Spring Integration来实现mqtt消息通讯。
Spring Integration 是一个基于 Spring 框架的消息传递框架,它提供了一种将不同应用程序之间的消息传递集成到企业应用程序中的方法。Spring Integration 提供了一组可重用的组件,这些组件可以用于构建消息驱动的应用程序。这些组件包括通道、消息处理器、转换器、适配器等。下图便是Spring Integration生产者-消费者模式。
编辑
编辑
1、简述Springboot Integration主要组件的功能:
- Message 包含 Header 和 Payload 两部分。
- MessageChannel 用于解耦生产者和消费者,实现消息发送。
- Service Activitor 用来绑定 MessageHandler 和用于消费消息的 MessageChannel。
- ChannelAdapter 用来连接 MessageChannel 和具体的消息端口,例如通信的 topic。
2、实现mqtt订阅的主要流程
- @IntegrationComponentScan,开启 Spring Integration 的注解扫描。
- 注入客户端工厂类 MqttPahoClientFactory,此处可以配置认证参数、超时时间等 broker 连接参数。
- 通过注入 MessageProducerSupport 的实例 MqttPahoMessageDrivenChannelAdapter,实现订阅 Topic 和绑定消息消费的 MessageChannel。
- 注入 MessageChannel 实例。
- 注入 MessageHandler 的实例,并通过 @ServiceActivator 绑定到对应的 MessageChannel。此处可配置消息处理的模式、QoS、默认的 Topic 等。
- 通过MessageChannel 的实例 DirectChannel 处理消费细节。Channel 消息后会发送给我们自定义的 MqttInboundMessageHandler 实例进行消费。
3、mqtt的代码实现
1、注入依赖项:
<!-- 添加mqtt依赖-->
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-core</artifactId>
</dependency>
<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>
<!--Hutool json处理-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.8</version>
</dependency>
2、编写配置类的代码:(需要自主在yml文件配置相应属性)
package com.xiaoxie.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@Data
@ConfigurationProperties(prefix = "publish.mqtt")
public class MqttProperties {
private String url;
private String clientid;
private String username;
private String password;
private boolean cleansession;
private String receiveTopics;
private int timeout;
private int keepalive;
private int connectionTimeout;
}
3、订阅端代码:
package com.xiaoxie.MqttServer;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import com.xiaoxie.pojo.Lock;
import com.xiaoxie.pojo.Student;
import com.xiaoxie.properties.MqttProperties;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.IntegrationComponentScan;
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.core.MqttPahoClientFactory;
import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;
import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.*;
import java.util.Date;
import java.util.UUID;
//Message 包含 Header 和 Payload 两部分。
// MessageChannel 用于解耦生产者和消费者,实现消息发送。
// MessageRouter 用来控制消息转发的 Channel。
// Service Activitor 用来绑定 MessageHandler 和用于消费消息的 MessageChannel。
// ChannelAdapter 用来连接 MessageChannel 和具体的消息端口,例如通信的 topic。
@Configuration
@IntegrationComponentScan
public class MqttInboundConfiguration {
@Autowired
private MqttProperties mqttProperties;
@Bean
public MessageChannel mqttInputChannel() {
return new DirectChannel();
}
@Bean
public MqttPahoClientFactory mqttInClient() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
String[] mqttServerUrls = mqttProperties.getUrl().split(",");
MqttConnectOptions options = new MqttConnectOptions();
options.setServerURIs(mqttServerUrls);
options.setUserName(mqttProperties.getUsername());
options.setPassword(mqttProperties.getPassword().toCharArray());
options.setKeepAliveInterval(mqttProperties.getKeepalive());
//接受离线消息
options.setCleanSession(false);
factory.setConnectionOptions(options);
return factory;
}
// 配置Client,监听Topic
// 如果要配置多个client,模仿此方法,多写几个client
@Bean
public MessageProducer inbound() {
String[] inboundTopics = mqttProperties.getReceiveTopics().split(",");
MqttPahoMessageDrivenChannelAdapter adapter =
// new MqttPahoMessageDrivenChannelAdapter(mqttProperties.getClientid() + "_inbound", mqttInClient(), mqttProperties.getReceiveTopics());
new MqttPahoMessageDrivenChannelAdapter(mqttProperties.getClientid() + "_inbound", mqttInClient(), inboundTopics);
adapter.setCompletionTimeout(1000 * 5);
adapter.setQos(0);
adapter.setOutputChannel(mqttInputChannel());
return adapter;
}
// 通过通道获取数据,即处理 MQTT 发送过来的消息,可以通过 MQTTX 工具发送数据测试
@Bean
//ServiceActivator注解表明当前方法用于处理MQTT消息,mqttInputChannel参数指定了用于接收消息信息的渠道
@ServiceActivator(inputChannel = "mqttInputChannel") // 异步处理
public MessageHandler handler() {
return new MessageHandler() {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
Object payload = message.getPayload();
MessageHeaders messageHeaders = message.getHeaders();
String recvTopic = (String) messageHeaders.get(MqttHeaders.RECEIVED_TOPIC);
//TODO 调用方法,对接收到的数据进行处理
updateOrStoreSensorData(recvTopic, payload);
}
};
}
private void updateOrStoreSensorData(String recvTopic, Object payload) {
if (recvTopic == "RFID"){
Student cmds = JSONUtil.toBean((String) payload, Student.class);
System.out.println(cmds.toString());
} else if (recvTopic == "lock") {
Lock lock = JSONUtil.toBean((String) payload, Lock.class);
System.out.println(lock.toString());
}
}
}
@ServiceActivator注解就是一个消息端点。消息端点的主要作用是以非侵入性方式将应用程序代码连接到消息传递框架。换句话说,理想情况下,应用程序代码应该不知道消息对象或消息管道。这类似于 MVC 范式中controller 的作用。正如controller 处理 HTTP 请求一样,消息端点处理消息。以及controller 映射到 URL 模式一样,消息端点映射到消息通道。这两种情况的目标是相同的。ServiceActivator是用于将服务实例连接到消息传递系统的通用端点。必须配置输入消息通道,如果要调用的服务方法能够返回值,还可以提供输出消息通道。具体流程如下: