目录
一、服务如何安装emqx(mqtt)docker 安装 emqx(mqtt)_282162974838632的博客-CSDN博客docker 安装 emqx(mqtt)https://blog.csdn.net/qq_51767561/article/details/127462070?spm=1001.2014.3001.5501
二、导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mqtt</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
三、配置文件内容
spring.main.banner-mode=off
server.compression.enabled=true
# mqtt
mqtt.protocol=tcp
mqtt.host=localhost
mqtt.port=8083
mqtt.url=${mqtt.protocol}://${mqtt.host}:${mqtt.port}
mqtt.username=admin
mqtt.password=admin
mqtt.client.name=mqttClient
mqtt.default.file.persistence.path=${user.dir}/data/mqClient
mqtt.default.subscribe.topic=topic1,topic2
mqtt.connect.timeout=10
mqtt.keep.alive.interval=10
mqtt.qos=2
mqtt.auto.reconnect=true
mqtt.clean.session=true
四、配置类
@Configuration
@Getter
@Slf4j
public class MqttConfiguration {
@Value("${mqtt.protocol:tcp}")
private String protocol;
@Value("${mqtt.url:}")
private String serverUri;
@Value("${mqtt.host:localhost}")
private String host;
@Value("${mqtt.port:8083}")
private Integer port;
@Value("${mqtt.username:admin}")
private String username;
@Value("${mqtt.password:guest}")
private String password;
@Value("${mqtt.client.name:client}")
private String clientName;
@Value("${mqtt.default.subscribe.topic:}")
private String[] topic;
@Value("${mqtt.connect.timeout:10}")
private Integer connectTimeout;
@Value("${mqtt.keep.alive.interval:10}")
private Integer keepAliveInterval;
@Value("${mqtt.qos:2}")
private Integer qos;
@Value("${mqtt.default.file.persistence.path}")
private String persistencePath;
@Value("${mqtt.clean.session:true}")
private boolean cleanSession;
@Value("${mqtt.auto.reconnect:true}")
private boolean autoReconnect;
@PostConstruct
public void initUri() {
if (!StringUtils.hasText(serverUri)) {
serverUri = protocol + "://" + host + ":" + port;
}
if (!StringUtils.hasText(persistencePath)) {
persistencePath = System.getProperty("user.dir") + "/data/mqClient";
}
}
/**
* MQTT连接器选项
*/
@Bean
public MqttConnectOptions mqttConnectOptions() {
MqttConnectOptions mqttConnectOptions = new MqttConnectOptions();
// 设置连接的地址
mqttConnectOptions.setServerURIs(new String[]{serverUri});
// 设置连接的用户名
mqttConnectOptions.setUserName(username);
// 设置连接的密码
mqttConnectOptions.setPassword(password.toCharArray());
// 设置是否清空session,这里如果设置为false表示服务器会保留客户端的连接记录,
// 把配置里的 cleanSession 设为false,客户端掉线后 服务器端不会清除session,
// 当重连后可以接收之前订阅主题的消息。当客户端上线后会接受到它离线的这段时间的消息
mqttConnectOptions.setCleanSession(cleanSession);
// 设置超时时间 单位为秒
mqttConnectOptions.setConnectionTimeout(connectTimeout);
//设置自动重新连接
mqttConnectOptions.setAutomaticReconnect(autoReconnect);
// 设置会话心跳时间 单位为秒 服务器会每隔10秒的时间向客户端发送心跳判断客户端是否在线
// 但这个方法并没有重连的机制
mqttConnectOptions.setKeepAliveInterval(keepAliveInterval);
// 设置“遗嘱”消息的话题,若客户端与服务器之间的连接意外中断,服务器将发布客户端的“遗嘱”消息。
mqttConnectOptions.setWill("willTopic", "WILL_DATA".getBytes(), 2, false);
return mqttConnectOptions;
}
}
MQTT 设计了 3 个 QoS 等级。
- QoS 0:消息最多传递一次,如果当时客户端不可用,则会丢失该消息。
- QoS 1:消息传递至少 1 次。
- QoS 2:消息仅传送一次。
QoS 0 是一种 "fire and forget" 的消息发送模式:Sender (可能是 Publisher 或者 Broker) 发送一条消息之后,就不再关心它有没有发送到对方,也不设置任何重发机制。
QoS 1 包含了简单的重发机制,Sender 发送消息之后等待接收者的 ACK,如果没收到 ACK 则重新发送消息。这种模式能保证消息至少能到达一次,但无法保证消息重复。
QoS 2 设计了重发和重复消息发现机制,保证消息到达对方并且严格只到达一次。
五、用户类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
private String name;
private String description;
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime createBy;
}
六、消息监听类
@Slf4j
public class DefaultMessageListener implements IMqttMessageListener {
private final String clientName;
public DefaultMessageListener(String clientName) {
this.clientName = clientName;
}
/**
* 处理消息
*
* @param topic 主题
* @param mqttMessage 消息
*/
@Override
public void messageArrived(String topic, MqttMessage mqttMessage) {
ObjectMapper mapper = new ObjectMapper();
try {
log.info(String.format("MQTT[" + clientName + "] 消息[" + mqttMessage.getId() + "]: 订阅主题[%s]发来消息[%s]", topic, mapper.readValue(mqttMessage.getPayload(), User.class)));
} catch (IOException e) {
e.printStackTrace();
}
}
}
七、服务类
@Service
@Slf4j
public class MqttService {
@Autowired
MqttConfiguration configuration;
@Autowired
MqttConnectOptions connectOptions;
/**
* 创建 mqtt 客户端
*/
public String createMqttClient(String clientId) {
if (isDuplicate(clientId)) {
return "用户:" + clientId + "已建立连接";
}
MqttClient client = null;
try {
String clientName = configuration.getClientName() + "-" + clientId;
// 配置 mqtt 持久性数据存储位置
MqttDefaultFilePersistence persistence = new MqttDefaultFilePersistence(configuration.getPersistencePath());
client = new MqttClient(configuration.getServerUri(), clientName, persistence);
client.connect(connectOptions);
initClientSubscribe(client, clientName);
StringPool.MQTT_CLIENTS.put(clientId, client);
} catch (MqttException e) {
log.error(String.format("MQTT: 客户端[%s]连接消息服务器[%s]失败", clientId, configuration.getServerUri()));
e.printStackTrace();
}
return "用户:" + clientId + "建立连接成功";
}
/**
* 删除 mqtt 客户端
*/
public String closeMqttClient(String clientId) {
if (!isDuplicate(clientId)) {
return "用户:" + clientId + "当前未建立连接";
}
try {
MqttClient client = getClient(clientId);
client.disconnect();
client.close();
StringPool.MQTT_CLIENTS.remove(clientId);
} catch (MqttException e) {
log.error("删除客户端{}失败", clientId);
e.printStackTrace();
}
return "用户:" + clientId + "关闭连接成功";
}
/**
* 发送消息
*
* @param clientId 发送信息的客户端的id
* @param topic 主题
* @param data 消息内容
*/
public void publish(String clientId, String topic, Object data) {
ObjectMapper mapper = new ObjectMapper();
try {
// 转换消息为json字符串
byte[] bytes = mapper.writeValueAsBytes(data);
MqttMessage mqttMessage = new MqttMessage(bytes);
mqttMessage.setQos(configuration.getQos());
getClient(clientId).publish(topic, mqttMessage);
} catch (JsonProcessingException e) {
log.error(String.format("MQTT: 主题[%s]发送消息转换json失败", topic));
} catch (MqttException e) {
log.error(String.format("MQTT: 主题[%s]发送消息失败", topic));
}
}
/**
* 订阅主题
*
* @param clientId 客户端的id
* @param topic 主题
*/
public void subscribe(String clientId, String topic) {
DefaultMessageListener listener = new DefaultMessageListener(configuration.getClientName());
subscribe(clientId, topic, listener);
}
/**
* 订阅主题
*
* @param clientId 客户端的id
* @param topic 主题
* @param listener 消息监听处理器
*/
public void subscribe(String clientId, String topic, IMqttMessageListener listener) {
try {
getClient(clientId).subscribe(topic, configuration.getQos(), listener);
} catch (MqttException e) {
log.error(String.format("MQTT: 订阅主题[%s]失败", topic));
}
}
/**
* 取消订阅主题
*
* @param clientId 客户端的id
* @param topic 主题
*/
public void unsubscribe(String clientId, String topic) {
try {
getClient(clientId).unsubscribe(topic);
} catch (MqttException e) {
log.error(String.format("MQTT: 订阅主题[%s]失败", topic));
}
}
/**
* 检查当前客户端是否已建立连接
*/
private boolean isDuplicate(String clientId) {
return StringPool.MQTT_CLIENTS.containsKey(clientId);
}
/**
* 获取客户端
*/
private MqttClient getClient(String clientId) {
return Optional.ofNullable(StringPool.MQTT_CLIENTS.get(clientId)).orElseThrow(() -> new RuntimeException("用户:" + clientId + "不存在"));
}
/**
* 初始化客户端的订阅
*/
private void initClientSubscribe(MqttClient client, String clientName) {
try {
int topicLen = configuration.getTopic().length;
IMqttMessageListener[] listeners = new IMqttMessageListener[topicLen];
int[] qos = new int[topicLen];
for (int i = 0; i < topicLen; i++) {
listeners[i] = new DefaultMessageListener(clientName);
qos[i] = configuration.getQos();
}
client.subscribe(configuration.getTopic(), qos, listeners);
} catch (MqttException e) {
log.error(String.format("MQTT: 订阅主题 %s 失败", Arrays.toString(configuration.getTopic())));
e.printStackTrace();
}
}
}
八、常量类
public class StringPool {
public final static ConcurrentHashMap<String, MqttClient> MQTT_CLIENTS = new ConcurrentHashMap<>();
}