1. MQTT简介
1.1MQTT简介
MQTT是一种基于 "发布订阅模式"的轻量级的消息传输协议!,是一种传统的客户端-服务器架构的替代方案
,因为一般传统的客户端-服务器是客户端能够直接和服务器进行通信完成消息的传输。发布订阅模式会将发送消息的发布者publisher与接收消息的订阅者subscribers进行分离
,publisher与subscribers 并不会直接通信,他们甚至都不清楚对方是否存在,他们之间的交流由第三方组件broker
代理。
MQTT的核心特性和概念可总结如下:
1.2MQTT特性
-
轻量级: 适用于处理能力、内存和能耗有限的物联网设备,因其低开销和小报文实现高效通信。
-
可靠性: 提供多种QoS等级、会话感知和持久连接,确保即使在高延迟或不稳定的网络中也能可靠传递消息。
-
安全通信: 通过TLS/SSL加密及用户名/密码认证或客户端证书,保障数据传输的机密性和访问控制。
-
双向通信: 发布-订阅模式支持设备间无缝双向通信,简化新设备集成并提高系统扩展性。
-
语言支持: 广泛支持多种编程语言(如PHP、Node.js、Python、Golang、Java等),实现跨平台和技术的无缝通信。
1.3MQTT核心概念
-
MQTT客户端: 任何运行MQTT客户端库的应用或设备,如即时通讯应用、传感器和测试工具。
-
MQTT Broker: 负责处理客户端请求(如连接、断开、订阅)及消息转发,支持大量连接和高消息吞吐量。
-
主题: UTF-8编码字符串,用于消息路由和分类,类似URL路径结构(如
chat/room/1
或sensor/10/temperature
),自动创建,无需手动管理。
1.4 控制报文简介
报文是网络中交换与传输的数据最小单元,通俗来讲就是站点一次性要发送的数据块
。它包含了将要发送的完整数据信息,其长短不一致,长度不限且可变。MQTT 客户端和服务端通过交换控制报文来完成它们的工作,比如订阅主题和发布消息,无论是什么类型的控制报文,它们都由固定报头、可变报头和有效载荷三个部分组成。
MQTT 目前定义了 15 种控制报文类型,按照功能进行分类,我们可以将这些报文分为连接、发布、订阅三个类别:
1.5 QoS简介
使用MQTT协议的设备大部分都是运行在网络受限
的环境下,而只依靠底层的TCP传输协议,并不能完全保证消息的可靠到达。
MQTT提供了QoS机制,其核心是设计了多种消息交互机制来提供不同的服务质量
,来满足用户在各种场景下对消息可靠性的要求。
MQTT 定义了三个 QoS 等级,分别为:
-
QoS 0,最多交付一次 -----> 可能丢失消息
QoS 0 是最低的 QoS 等级。QoS 0 消息即发即弃,不需要等待确认,不需要存储和重传,因此对于接收方来说,永远都不需要担心收到重复的消息。 -
QoS 1,至少交付一次 -----> 可以保证收到消息,但消息可能重复
为了保证消息到达,QoS 1 加入了应答与重传机制,发送方只有在收到接收方的 PUBACK 报文以后,才能认为消息投递成功,在此之前,发送方需要存储该 PUBLISH 报文以便下次重传。 -
QoS 2,只交付一次 -----> 可以保证消息既不丢失也不重复
QoS 2 解决了 QoS 0、1 消息可能丢失或者重复的问题,但相应地,它也带来了最复杂的交互流程和最高的开销。每一次的 QoS 2 消息投递,都要求发送方与接收方
进行至少两次请求/响应流程。
1.6主题详解
MQTT 主题通配符包含单层通配符 +
及多层通配符 #
,主要用于客户端一次订阅多个主题
。
2. MQTT实战场景搭建
2.1 代理服务器:EMQX部署
部署教程:https://docs.emqx.com/zh/enterprise/latest/deploy/install-docker.html
拉取镜像
docker pull emqx/emqx-enterprise:5.8.1
运行容器
docker run -d --name emqx-enterprise -p 1883:1883 -p 8083:8083 -p 8084:8084 -p 8883:8883 -p 18083:18083 emqx/emqx-enterprise:5.8.1
常见端口介绍:
端口号 | 说明 |
---|---|
1883 | TCP端口 |
8083 | WebSocket端口 |
8084 | WebSocket Secure 端口 |
8883 | SSL/TLS 端口 |
18083 | Broker的Dashboard访问端口号 |
2.2 客户端:MQTTX软件使用
MQTTX 是EMQX开源的一款跨平台 MQTT 5.0 客户端工具,它支持 macOS, Linux 并且支持自定义脚本模拟测试、MQTT 消息格式转换、日志记录等多个功能。
官网地址:https://mqttx.app/zh
使用教程不在赘述。
2.3 整合springboot
基础配置搭建
<dependencies>
<!-- spring boot项目web开发的起步依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- spring boot项目集成消息中间件基础依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<!-- spring boot项目和mqtt客户端集成起步依赖 -->
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mqtt</artifactId>
<version>5.4.3</version>
</dependency>
<!-- lombok依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- fastjson依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
</dependencies>
启动类添加配置
@EnableConfigurationProperties(value = MqttConfigurationProperties.class)
添加属性配置
spring.mqtt.username=admin
spring.mqtt.password=jin15738121358
spring.mqtt.url=tcp://192.168.253.166:1883
spring.mqtt.subClientId=sub_client_id_123
spring.mqtt.subTopic=atguigu/iot/lamp/#
spring.mqtt.pubClientId=pub_client_id_123
创建实体类读取自定义配置
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "spring.mqtt")
public class MqttConfigurationProperties {
private String username;
private String password;
private String url;
private String subClientId ;
private String subTopic ;
private String pubClientId ;
}
创建配置类配置链接工厂
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
@Configuration
public class MqttConfiguration {
@Autowired
private MqttConfigurationProperties mqttConfigurationProperties ;
@Bean
public MqttPahoClientFactory mqttClientFactory(){
// 创建客户端工厂
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
// 创建MqttConnectOptions对象
MqttConnectOptions options = new MqttConnectOptions();
options.setCleanSession(true);
options.setUserName(mqttConfigurationProperties.getUsername());
options.setPassword(mqttConfigurationProperties.getPassword().toCharArray());
options.setServerURIs(new String[]{mqttConfigurationProperties.getUrl()});
factory.setConnectionOptions(options);
// 返回
return factory;
}
}
订阅主题获取消息
配置入站适配器
import org.springframework.beans.factory.annotation.Autowired;
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.MqttPahoClientFactory;
import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;
import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
@Configuration
public class MqttInboundConfiguration {
@Autowired
private MqttConfigurationProperties mqttConfigurationProperties ;
@Autowired
private ReceiverMessageHandler receiverMessageHandler;
/**
* 配置消息传输通道
* @return
*/
@Bean
public MessageChannel mqttInputChannel() {
return new DirectChannel();
}
/**
* 配置入站适配器
*/
@Bean
public MessageProducer messageProducer(MqttPahoClientFactory mqttPahoClientFactory) {
MqttPahoMessageDrivenChannelAdapter adapter =
new MqttPahoMessageDrivenChannelAdapter(mqttConfigurationProperties.getUrl() ,
mqttConfigurationProperties.getSubClientId() ,
mqttPahoClientFactory , mqttConfigurationProperties.getSubTopic().split(",")) ;
adapter.setConverter(new DefaultPahoMessageConverter());
adapter.setQos(1);
adapter.setOutputChannel(mqttInputChannel());
return adapter ;
}
/**
* 配置入站消息处理器
* @return
*/
@Bean
@ServiceActivator(inputChannel = "mqttInputChannel")
public MessageHandler messageHandler() {
return this.receiverMessageHandler ;
}
}
定义监听主题消息的处理器
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.MessagingException;
import org.springframework.stereotype.Component;
@Component
public class ReceiverMessageHandler implements MessageHandler {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
MessageHeaders headers = message.getHeaders();
String receivedTopicName = (String) headers.get("mqtt_receivedTopic");
System.out.println("receivedTopicName:"+receivedTopicName);
System.out.println("接收到消息:" + message.getPayload());
}
}
配置出站消息处理器
import org.springframework.beans.factory.annotation.Autowired;
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.mqtt.core.MqttPahoClientFactory;
import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
@Configuration
public class MqttOutboundConfiguration {
@Autowired
private MqttConfigurationProperties mqttConfigurationProperties ;
@Autowired
private MqttPahoClientFactory pahoClientFactory ;
@Bean
public MessageChannel mqttOutputChannel() {
return new DirectChannel();
}
@Bean
@ServiceActivator(inputChannel = "mqttOutputChannel")
public MessageHandler mqttOutboundMassageHandler() {
MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler(mqttConfigurationProperties.getUrl() ,
mqttConfigurationProperties.getPubClientId() , pahoClientFactory ) ;
messageHandler.setAsync(true);
messageHandler.setDefaultQos(0);
messageHandler.setDefaultTopic("default");
return messageHandler ;
}
}
2、定义发送消息的网关接口
import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.handler.annotation.Header;
@MessagingGateway(defaultRequestChannel = "mqttOutputChannel")
public interface MqttGateway {
/**
* 发送mqtt消息
* @param topic 主题
* @param payload 内容
*/
void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, String payload);
/**
* 发送包含qos的消息
* @param topic 主题
* @param qos 对消息处理的几种机制。
* * 0 表示的是订阅者没收到消息不会再次发送,消息会丢失。<br>
* * 1 表示的是会尝试重试,一直到接收到消息,但这种情况可能导致订阅者收到多次重复消息。<br>
* * 2 多了一次去重的动作,确保订阅者收到的消息有一次。
* @param payload 消息体
*/
void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, @Header(MqttHeaders.QOS) int qos, String payload);
}
定义发送消息的服务类
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
@AllArgsConstructor
public class MqttMessageSender {
private MqttGateway mqttGateway;
/**
* 发送mqtt消息
* @param topic 主题
* @param message 内容
*/
public void send(String topic, String message) {
mqttGateway.sendToMqtt(topic, message);
}
/**
* 发送包含qos的消息
* @param topic 主题
* @param qos 质量
* @param message 消息体
*/
public void send(String topic, int qos, byte[] message){
mqttGateway.sendToMqtt(topic, qos, Arrays.toString(message));
}
}