EMQX安装文档
下载安装
选择开源版本的centos版本amd的rpm
https://www.emqx.com/zh/downloads-and-install/broker?os=RHEL
命令行下载安装
登录linux服务器,使用命令行下载安装emqx
#进入opt目录,将文件下载到此目录下
cd /opt
#使用官方文档命令行下载安装
wget https://www.emqx.com/zh/downloads/broker/5.7.0/emqx-5.7.0-el7-amd64.rpm
sudo yum install emqx-5.7.0-el7-amd64.rpm -y
#启动
sudo systemctl start emqx
#打开防火墙端口号
firewall-cmd --zone=public --add-port=1883/tcp --permanent
firewall-cmd --zone=public --add-port=8080/tcp --permanent
firewall-cmd --zone=public --add-port=8083/tcp --permanent
firewall-cmd --zone=public --add-port=8084/tcp --permanent
firewall-cmd --zone=public --add-port=8883/tcp --permanent
firewall-cmd --zone=public --add-port=18083/tcp --permanent
# 防火墙配置立即生效
firewall-cmd --reload
阿里云开放端口
阿里云安全组开放端口号1883,8080,8083,8084,8883,18083
使用
使用外网访问
http://服务器公网IP地址:18083/
默认账户:admin
默认密码:public
下载客户端软件
https://mqttx.app/zh/downloads
客户端连接
连接成功
设置连接客户端认证方式
配置内置服务器
目前为止,连接服务器的MQTT服务,是没有做校验的,输入任何客户端名称和密码都可以连接。但对于产品来言,这种认证方式是不安全的。最开始安装emqx时,我们先使用内置服务器做一个简单的连接测试。
选择内置数据库的方式
选择clinetnId的方式
点击用户管理。添加可以连接的客户端用户
此时不输入密码和clientId就无法连接,重新输入信息再次连接
连接成功
配置MySQL连接方式
正式使用时,使用数据库连接的方式
新建数据库mqtt_user并创建表和插入数据
#mqtt_user数据库中创建表mqtt_user
CREATE TABLE `mqtt_user` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(100) DEFAULT NULL,
`password_hash` varchar(100) DEFAULT NULL,
`salt` varchar(35) DEFAULT NULL,
`is_superuser` tinyint(1) DEFAULT 0,
`created` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `mqtt_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
#插入第一个客户端用户 ,用户名emqx_u,密码public
INSERT INTO mqtt_user(username, password_hash, salt, is_superuser) VALUES ('emqx_u', SHA2(concat('public', 'slat_foo123'), 256), 'slat_foo123', 1);
Query OK, 1 row affected (0,01 sec)
#插入第二个客户端用户 ,用户名emqx_u1,密码public
INSERT INTO mqtt_user(username, password_hash, salt, is_superuser) VALUES ('emqx_u1', SHA2(concat('public', 'slat_foo123'), 256), 'slat_foo123', 1);
Query OK, 1 row affected (0,01 sec)
配置成功
使用MQTTX客户端连接
连接成功
编码实现设备上下线和消息接收功能
引入依赖
<!--integration用于在 Spring Boot 项目中集成 MQTT(消息队列遥测传输)协议-->
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mqtt</artifactId>
</dependency>
<!--用于Java的MQTT(消息队列遥测传输)客户端库依赖-->
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>1.2.5</version>
</dependency>
配置客户端信息
mqtt:
brokerUrl: tcp://服务器公网IP地址:1883
clientId: emqx_u
clientName: emqx_u
password: public
topics: AAA/#,BBB/#,$SYS/brokers/+/clients/#
配置
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.IntegrationComponentScan;
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.messaging.MessageChannel;
@Configuration
@IntegrationComponentScan
public class MqttConfig {
@Value("${mqtt.brokerUrl}")
private String brokerUrl;
@Value("${mqtt.clientId}")
private String clientId;
@Value("${mqtt.clientName}")
private String clientName;
@Value("${mqtt.password}")
private String password;
@Value("${mqtt.topics}")
private String topics;
/**
* 创建并注册一个名为 mqttInputChannel 的消息通道,该通道允许消息在发送者和接收者之间进行直接、同步的传递。这个通道随后会被用于处理从 MQTT 代理接收到的消息。
* @Author Hootin
* @return
*/
@Bean
public MessageChannel mqttInputChannel() {
return new DirectChannel();
}
//创建MQTT客户端的工厂类
@Bean
public MqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
MqttConnectOptions options = new MqttConnectOptions();
// 设置连接选项,如用户名、密码、超时等(如果需要)
options.setUserName(clientName);
options.setPassword(password.toCharArray());
factory.setConnectionOptions(options);
return factory;
}
@Bean
public MessageProducer inbound() {
/**
* MqttPahoMessageDrivenChannelAdapter是Spring Integration 提供的 MQTT 入站通道适配器(inbound channel adapter)。
* 它负责连接到 MQTT 代理,并监听指定的 MQTT 主题(topics)上的消息。
* 当消息到达时,它将这些消息转换为 Spring Integration 消息,并发送到与适配器关联的输入通道(input channel)。
*/
MqttPahoMessageDrivenChannelAdapter adapter =
new MqttPahoMessageDrivenChannelAdapter(brokerUrl,clientId(), mqttClientFactory(), topics().split(","));
adapter.setCompletionTimeout(5000);
adapter.setConverter(new DefaultPahoMessageConverter());
adapter.setQos(1);
adapter.setOutputChannel(mqttInputChannel());
return adapter;
}
@Bean
public String clientId() {
return clientId;
}
@Bean
public String topics() {
return topics;
}
}
监听
import cn.hutool.json.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Service;
import java.text.SimpleDateFormat;
import java.util.Date;
@Service
@Slf4j
public class MqttMessageHandler {
/**
* @ServiceActivator 是 Spring Integration 中的一个注解,用于定义消息处理流程中的一个步骤。当消息到达指定的通道(channel)时,由该注解标记的方法将被调用以处理该消息。
* inputChannel = "mqttInputChannel":这个属性指定了该方法应该监听哪个输入通道。
* 因此,当 mqttInputChannel 上有消息到达时,这个被 @ServiceActivator 标记的方法将被调用。
* 消息处理:在该注解下的方法定义如何处理从 mqttInputChannel 接收到的消息。
* @param message
*/
@ServiceActivator(inputChannel = "mqttInputChannel")
public void handleMessage(Message<?> message) {
String payload = (String) message.getPayload(); // 假设消息是String类型
JSONObject jsonObject = new JSONObject(payload);
// 这里可以根据payload内容判断是上线还是下线,并执行相应的逻辑
if (isOfflineMessage(payload)) {
// 处理设备离线逻辑
//获取24小时制格式的离线时间字符串
String disconnected_at = strConvertFormattedDate(String.valueOf(jsonObject.get("disconnected_at")));
String connected_at = strConvertFormattedDate(String.valueOf(jsonObject.get("connected_at")));
//打印客户端ID,上线时间,离线时间
log.info("设备{}在{}离线,上线时间为{}",jsonObject.get("clientid").toString(),disconnected_at,connected_at);
} else {
// 处理设备上线逻辑(注意:上线通常不是通过LWT消息来识别的)
//获取上线时间和设备客户端ID打印
if (payload.contains("connected_at")){
String connected_at = strConvertFormattedDate(String.valueOf(jsonObject.get("connected_at")));
log.info("设备{}上线,上线时间为{}",jsonObject.get("clientid").toString(),connected_at);
}else {
log.info("设备发送消息"+payload);
}
}
}
private boolean isOfflineMessage(String payload) {
// 根据LWT消息内容来判断是否是离线消息,如果信息包含断开连接时间,就说明这条消息是离线消息
return payload.contains("disconnected_at");
}
public Date strConvertDate(String str){
return new Date(Long.valueOf(str));
}
//将时间戳字符串转换为24小时制格式的时间字符串
public String strConvertFormattedDate(String str){
Date date = new Date(Long.valueOf(str));
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return simpleDateFormat.format(date);
}
}
MQTTX测试
客户端填写信息:
使用添加的第二个客户端,第一个客户端用于编码时使用
Name:emqx_u1
Client ID: emqx_u1
Host:mqtt://服务器公网IP地址
Port:1883
Username:emqx_u1
password:public
连接
发送消息
下线
控制台输出
2024-06-27T11:25:00.389+08:00 INFO 3032 — [wjzb-car-manager-mqtt] [TT Call: emqx_u] c.wjzb.mqtt.service.MqttMessageHandler : 设备emqx_u1上线,上线时间为2024-06-27 11:24:57
2024-06-27T11:25:03.749+08:00 INFO 3032 — [wjzb-car-manager-mqtt] [TT Call: emqx_u] c.wjzb.mqtt.service.MqttMessageHandler : 设备发送消息{
“msg”: “hello”
}
2024-06-27T11:25:05.104+08:00 INFO 3032 — [wjzb-car-manager-mqtt] [TT Call: emqx_u] c.wjzb.mqtt.service.MqttMessageHandler : 设备emqx_u1在2024-06-27 11:25:02离线,上线时间为2024-06-27 11:24:57
-27T11:25:03.749+08:00 INFO 3032 — [wjzb-car-manager-mqtt] [TT Call: emqx_u] c.wjzb.mqtt.service.MqttMessageHandler : 设备发送消息{
“msg”: “hello”
}
2024-06-27T11:25:05.104+08:00 INFO 3032 — [wjzb-car-manager-mqtt] [TT Call: emqx_u] c.wjzb.mqtt.service.MqttMessageHandler : 设备emqx_u1在2024-06-27 11:25:02离线,上线时间为2024-06-27 11:24:57