内容提要
- MQTT概述
- 案例解析
一、MQTT概述
1.1 MQTT的简介与作用
MQTT(Message Queuing Telemetry Transport)是一种基于发布/订阅模式的轻量级通讯协议,旨在为资源受限的设备提供高效、可靠的消息传输服务,特别适合于低带宽、高延迟或不可靠的网络环境。
1、MQTT协议的主要特点
- 轻量级:MQTT协议设计得非常轻量级,占用极少的代码和带宽,适用于资源受限的设备。
- 发布/订阅模式:MQTT通过发布/订阅模式实现消息的传递,支持一对多的消息发布和接收,有效减少了网络流量和资源使用。
- 服务质量(QoS):MQTT协议支持不同的服务质量等级,确保消息的可靠传递。
- 心跳机制:通过心跳机制监测连接状态,减少网络重新连接的开销。
- 安全性:支持认证和加密技术,确保通信过程中的数据安全。
2、MQTT协议广泛应用于各种场景
- 物联网(IoT):在智能家居、工业自动化、远程监控等领域,MQTT使得设备之间能够实时、可靠地交换信息。
- 移动应用和小型设备:MQTT的低带宽消耗和高效资源利用,使其在移动设备和传感器网络中广泛应用。
- 车联网通信:在汽车、制造、石油、天然气等行业市场,MQTT协议被用于车辆通信和数据采集。
3、MQTT协议的基本组件
- 发布者(Publisher):将消息发布到特定的主题(Topic)。
- 代理(Broker):作为中介,接收发布者的消息并将其传递给已订阅该主题的订阅者。
- 订阅者(Subscriber):订阅特定的主题以接收相关消息。
说明:MQTT的安装有完全独立完装与基本RabbitMQ的安装,接下来我们将在RabbitMQ中开启MQTT协议。
1.2 RabbitMQ中MQTT在windows中安装与配置
在Windows中安装RabbitMQ和MQTT插件的步骤如下。
1、首先在Windows中安装RabbitMQ
参考本站:消息队列RabbitMQ在Windows中安装与配置完全解析_rabbitmq windows-CSDN博客
2、安装MQTT插件
(1)打开RabbitMQ的sbin目录
(2)地址栏输入cmd,切换到Windows的命令行
(3)运行以下命令来安装MQTT插件
rabbitmq-plugins enable rabbitmq_mqtt
这将同时会启用RabbitMQ的MQTT插件,提供与后端服务交互使用,对应端口1883。
完成以上操作后,我们就可以在Windows系统中使用RabbitMQ和MQTT了。如果需要进一步配置MQTT,可以查看RabbitMQ官方文档中关于MQTT插件的配置部分。
二、案例解析
2.1 RabbitMQ下开启MQTT
默认情况下RabbitMQ是不开启MQTT 协议的,所以需要我们手动的开启相关的插件,而RabbitMQ的MQTT 协议分为两种。
(1)第一种 rabbitmq_mqtt 提供与后端服务交互使用
对应端口1883(上边已安装过)
rabbitmq-plugins enable rabbitmq_mqtt
(2)第二种 rabbitmq_web_mqtt 提供与前端交互使用
对应端口15675
rabbitmq-plugins enable rabbitmq_web_mqtt
2.2 Java案例代码
2.2.1 引入Maven依赖
创建Springboot项目,在pom.xml中引入依赖spring-integration-mqtt
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.7.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.yhc</groupId>
<artifactId>mqtt-push</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mqtt</name>
<description>demo project</description>
<properties>
<java.version>1.8</java.version>
<paho.client.mqttv3.version>1.2.5</paho.client.mqttv3.version>
</properties>
<!--导入依赖包-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--mqtt依赖包-->
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mqtt</artifactId>
</dependency>
<!-- lombok工具包 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
2.2.2 MQTT公共的配置信息
包括clientId和serverClientId,默认的topic信息, 以及连接rabbitmq的用户名和密码。
1、clientId的唯一性设置
这里可以使用随机数或者分布式id来生成clientId。
此处clientId直接在代码中写死了,服务为单实例部署,运行没有什么问题。
如果是分布式项目,则需另行处理。
public class MQTTProducer{
/**
* MQ4IOT clientId,由业务系统分配,需要保证每个tcp连接都不一样,保证全局唯一,如果不同的客户端对象(tcp 连接)使用了相同的 clientId 会导致连接异常断开。
* clientId 由两部分组成,格式为 GroupID@@@DeviceId,其中 groupId在MQ4IOT控制台申请,DeviceId 由业务方自己设置,clientId 总长度不得超过64个字符。
*/
private String clientId = "GID_XXXXX@@@XXXXX";
}
2、yaml配置文件application.yaml
server:
port: 8090
mqtt-push:
# clientId的前缀
clientId: mqtt_client_
# 通过通配符 "+", 订阅主题, 就可以接收所有传感器发送的温度数据了
# 多个的话通过,号分割
defaultTopic: sensor/+/temperature
# clientId的前缀
serverClientId: mqtt_server_
# 这边替换为自己的ip(或者域名)
# 多个的话通过,号分割
servers: tcp://127.0.0.1:1883
# 访问rabbitmq的用户名
username: guest
# 访问rabbitmq的密码
password: guest
3、MQTT通用配置信息
package com.yhc.config;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.integration.annotation.IntegrationComponentScan;
import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
import org.springframework.stereotype.Component;
/**
* mqtt连接配置
*/
@Data
@Component
@IntegrationComponentScan
@ConfigurationProperties(prefix = "mqtt-push")
public class MqttConfig {
// 服务地址
private String servers;
//客户端id
private String clientId;
//服务端id
private String serverClientId;
//默认主题
private String defaultTopic;
//用户名
private String username;
//密码
private String password;
/**
* 创建MqttPahoClientFactory,设置MQTT Broker连接属性
* 如果使用SSL验证,也在这里设置
* @return MqttPahoClientFactory
*/
@Bean(name = "mqttClientFactory")
public MqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
factory.setConnectionOptions(mqttConnectOptions());
return factory;
}
@Bean(name = "mqttConnectOptions")
public MqttConnectOptions mqttConnectOptions() {
MqttConnectOptions options = new MqttConnectOptions();
//断开后,是否自动连接
options.setAutomaticReconnect(true);
// 设置是否清空session,这里如果设置为false表示服务器会保留客户端的连接记录;
// 把配置里的 cleanSession 设为false,客户端掉线后 服务器端不会清除session;
// 当重连后可以接收之前订阅主题的消息。当客户端上线后会接受到它离线的这段时间的消息
options.setCleanSession(false);
// 设置连接的用户名
options.setUserName(username);
// 设置连接的密码
options.setPassword(password.toCharArray());
options.setServerURIs(servers.split(","));
// 设置超时时间 单位为秒
options.setConnectionTimeout(10);
// 设置会话心跳时间 单位为秒 服务器会每隔1.5*20秒的时间向客户端发送心跳判断客户端是否在线,但这个方法并没有重连的机制
options.setKeepAliveInterval(20);
// 设置‘遗嘱’消息的话题,若客户端与服务器之间的连接意外中断,服务器将发布客户端的“遗嘱”消息。
// options.setWill("willTopic", WILL_DATA, 2, false);
return options;
}
}
2.2.3 发布消息配置
package com.yhc.config;
import com.mqttpush.constant.MqttConstant;
import com.mqttpush.util.RandomUtil;
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.outbound.MqttPahoMessageHandler;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import javax.annotation.Resource;
/**
* mqtt 消息发布配置
*/
@Configuration
public class MqttProducerConfig {
@Resource
private MqttConfig mqttConfig;
@Bean
public MessageChannel mqttOutboundChannel() {
return new DirectChannel();
}
/**
* ServiceActivator注解表明:当前方法用于处理MQTT消息,inputChannel参数指定了用于消费消息的channel。
* @return
*/
@Bean
@ServiceActivator(inputChannel = MqttConstant.CHANNEL_NAME_OUT)
public MessageHandler mqttOutbound() {
// clientId相同导致客户端间相互竞争消费
// 同一时间内只能有一个客户端能拿到消息, 其他客户端不但不能消费消息,而且还在不断的掉线重连:Lost connection: 已断开连接; retrying...。
MqttPahoMessageHandler messageHandler
= new MqttPahoMessageHandler(mqttConfig.getServerClientId() + MqttConstant.MESSAGE_HANDLER_CLIENT_ID_PRODUCER + RandomUtil.getRandomStr(),
mqttConfig.mqttClientFactory());
messageHandler.setAsync(true);
// MQTT 提供了三种服务质量(QoS),在不同网络环境下保证消息的可靠性。
// QoS 0:消息最多传送一次。如果当前客户端不可用,它将丢失这条消息。
// QoS 1:消息至少传送一次。
// QoS 2:消息只传送一次。
messageHandler.setDefaultQos(1);
messageHandler.setDefaultTopic(mqttConfig.getDefaultTopic());
return messageHandler;
}
}
2.2.4 订阅消息配置
通信是双向的,服务端既可以发布消息,也可以订阅消息。
package com.yhc.config;
import com.mqttpush.constant.MqttConstant;
import com.mqttpush.util.RandomUtil;
import lombok.extern.slf4j.Slf4j;
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.inbound.MqttPahoMessageDrivenChannelAdapter;
import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import javax.annotation.Resource;
/**
* mqtt 消息订阅配置
*/
@Slf4j
@Configuration
public class MqttSubscriberConfig {
@Resource
private MqttConfig mqttConfig;
@Bean
public MessageChannel mqttInputChannel() {
return new DirectChannel();
}
@Bean
public MessageProducer inbound() {
// clientId相同导致客户端间相互竞争消费
// 同一时间内只能有一个客户端能拿到消息, 其他客户端不但不能消费消息,而且还在不断的掉线重连:Lost connection: 已断开连接; retrying...。
MqttPahoMessageDrivenChannelAdapter adapter
= new MqttPahoMessageDrivenChannelAdapter(mqttConfig.getClientId() + MqttConstant.MESSAGE_HANDLER_CLIENT_ID_CONSUMER + RandomUtil.getRandomStr(),
mqttConfig.mqttClientFactory(),
mqttConfig.getDefaultTopic());
adapter.setCompletionTimeout(5000);
adapter.setConverter(new DefaultPahoMessageConverter());
adapter.setQos(2);
adapter.setOutputChannel(mqttInputChannel());
return adapter;
}
/**
* 消息订阅, 处理收到的消息
*/
@Bean
@ServiceActivator(inputChannel = MqttConstant.CHANNEL_NAME_IN)
public MessageHandler mqttInMessageHandler() {
return message -> {
try {
// 消息体
String payload = message.getPayload().toString();
log.info("接收到消息, 内容: {}", payload);
// byte[] bytes = (byte[]) message.getPayload(); // 收到的消息是字节格式
// 消息的topic
String topic = message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC).toString();
log.info("接收到消息, topic: {}", topic);
// 根据主题分别进行消息处理。
if (topic.matches(".+/sensor")) { // 匹配:1/sensor
String sensorSn = topic.split("/")[0];
log.info("传感器" + sensorSn + ": 的消息: " + payload);
} else if (topic.equals("collector")) {
log.info("采集器的消息:" + payload);
} else {
log.info("丢弃消息:主题[" + topic + "],负载:" + payload);
}
} catch (MessagingException ex) {
//logger.info(ex.getMessage());
}
};
}
}
2.2.5 常量类
package com.yhc.constant;
public class MqttConstant {
//mqtt发布者信道名称
public static final String CHANNEL_NAME_OUT = "mqttOutboundChannel";
// mqtt接收信道名称
public static final String CHANNEL_NAME_IN = "mqttInputChannel";
// mqtt消息发布者, serverClientId的前缀
public static final String MESSAGE_HANDLER_CLIENT_ID_PRODUCER = "producer";
// mqtt消息接收者, clientId的前缀
public static final String MESSAGE_HANDLER_CLIENT_ID_CONSUMER = "consumer";
}
2.2.6 发布消息
使用@MessagingGateway注解发送消息
package com.yhc.producer;
import com.mqttpush.constant.MqttConstant;
import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.handler.annotation.Header;
/**
* rabbitmq mqtt协议网关接口
*/
@MessagingGateway(defaultRequestChannel = MqttConstant.CHANNEL_NAME_OUT)
public interface MqttGateway {
void sendMessage2Mqtt(String data);
void sendMessage2Mqtt(String data, @Header(MqttHeaders.TOPIC) String topic);
void sendMessage2Mqtt(@Header(MqttHeaders.TOPIC) String topic, @Header(MqttHeaders.QOS) int qos, String payload);
}
2.2.7 控制器和测试类
控制器类:
package com.yhc.controller;
import com.mqttpush.dto.ReqSendMsgDTO;
import com.mqttpush.producer.MqttGateway;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
* mqtt发送消息控制器
*/
@RestController
@RequestMapping("/mqtt")
public class MqttController {
@Resource
private MqttGateway mqttGateway;
@PostMapping("/sendMessage")
public String sendMqtt(@RequestBody ReqSendMsgDTO reqSendMsgDTO) {
mqttGateway.sendMessage2Mqtt(reqSendMsgDTO.getTopic(), reqSendMsgDTO.getPayload());
return "SUCCESS";
}
}
测试类:
package com.yhc.test;
import com.mqttpush.producer.MqttGateway;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
@SpringBootTest
class MqttPushApplicationTests {
@Resource
private MqttGateway mqttGateway;
@Test
void contextLoads() {
}
@Test
public void sendMqtt() {
mqttGateway.sendMessage2Mqtt("mqtt/test", "这里是topic的内容");
}
}
2.3 案例实测
2.3.1 MQTTBOX连接配置
2.3.2 MQTTBOX 发送和订阅配置
左边为发布消息,右边为订阅消息
2.3.3 发送消息
可以使用Postman发送消息或MQTTBOX
1、使用Postman发送消息
(1) 服务器订阅消息
(2) MQTTBOX订阅消息
2、MQTTBOX发送消息
(1)通过代码订阅消息
(2)MQTTBOX订阅消息
更多精彩内容请持续关注本站!