直接上代码,使用的pom依赖为
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-mqtt</artifactId>
<version>4.5.7</version>
</dependency>
具体实现代码如下
package com.vert;
import io.netty.handler.codec.mqtt.MqttProperties;
import io.netty.handler.codec.mqtt.MqttQoS;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.mqtt.MqttEndpoint;
import io.vertx.mqtt.MqttServer;
import io.vertx.mqtt.MqttTopicSubscription;
import io.vertx.mqtt.MqttWill;
import io.vertx.mqtt.messages.codes.MqttSubAckReasonCode;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/*
* 缺少
* */
public class MqttVertical extends AbstractVerticle {
private static Map<String, List<MqttEndpoint>> topicSubscribers;
//存储每个topic的订阅关系
private static Map<MqttEndpoint, List<String>> subscriptions;
//存储每个设备订阅的topic
@Override
public void start() throws Exception {
Vertx vertx = Vertx.vertx();
MqttServer mqttServer = MqttServer.create(vertx);
topicSubscribers = new HashMap<>();
subscriptions=new HashMap<>();
mqttServer.endpointHandler(endpoint -> {
//使用lambda表达式实现了endpointHandler方法,传入参数endpoint;
System.out.println("MQTT client [" + endpoint.clientIdentifier() + "] request to connect, clean session = " + endpoint.isCleanSession());
if (endpoint.auth() != null) {
System.out.println("[username = " + endpoint.auth().getUsername() + ", password = " + endpoint.auth().getPassword() + "]");
}
System.out.println("[properties = " + endpoint.connectProperties() + "]");
// if(willTopic != null && !"".equals(willTopic)){
// MqttWill will = new MqttWill(true, willTopic, willMessage,willQos, true, willProperties);
// accept connection from the remote client
// endpoint.accept(false,will);
// }
endpoint.exceptionHandler(e -> {
if(e instanceof IOException){
Buffer willMessage = endpoint.will().getWillMessage();
MqttProperties willProperties = endpoint.will().getWillProperties();
int willQos = endpoint.will().getWillQos();
String willTopic = endpoint.will().getWillTopic();
System.out.println("异常: " + e.getMessage());
e.printStackTrace();
}
});
endpoint.accept(false);
SubscribeHandle(endpoint);
UnSubscribeHandle(endpoint);
ReceiveHandle(endpoint);
DisConnectHandle(endpoint);
});
mqttServer.listen(ar -> {
if (ar.succeeded()) {
System.out.println("MQTT server is listening on port " + ar.result().actualPort());
} else {
System.out.println("Error on starting the server");
ar.cause().printStackTrace();
}
});
}
/*
* 设备断开处理
* */
private void DisConnectHandle(MqttEndpoint endpoint) {
endpoint.disconnectMessageHandler(disconnectMessage -> {
System.out.println("Received disconnect from client, reason code = " + disconnectMessage.code());
System.out.println("Client "+endpoint.auth().getUsername()+"disconnect with");
//对两个映射订阅关系的列表进行更新
for (String topic:subscriptions.get(endpoint)){
topicSubscribers.get(topic).remove(endpoint);
System.out.println("["+topic+"]");
}
subscriptions.remove(endpoint);
});
}
/*
*处理订阅消息
* */
private void SubscribeHandle(MqttEndpoint endpoint) {
endpoint.subscribeHandler(subscribe -> {
Boolean IsValidTopic=false;
//存储订阅消息中要订阅的topic的列表
List<MqttTopicSubscription> topicSubscriptions = subscribe.topicSubscriptions();
//存储订阅topic Qos级别的列表
List<MqttSubAckReasonCode> reasonCodes = new ArrayList<>();
//遍历列表
for (MqttTopicSubscription s : topicSubscriptions) {
//topic
String topic = s.topicName();
//Qos级别
MqttQoS qos = s.qualityOfService();
//判断topic是否合法
if (!isValidTopic(topic)){
//不合法则向设备发送消息
endpoint.publish(topic, Buffer.buffer("非法topic,topic不可包含空格"), qos, false, false);
continue;
}else {
IsValidTopic=true;
}
System.out.println("Subscription for " + topic + " with QoS " + qos);
reasonCodes.add(MqttSubAckReasonCode.qosGranted(qos));
//判断是否已有此topic,如果有则直接添加,没有则新建键值对
if (!topicSubscribers.containsKey(topic)) {
topicSubscribers.put(topic, new ArrayList<MqttEndpoint>());
}
topicSubscribers.get(topic).add(endpoint);
//同上
if (!subscriptions.containsKey(endpoint)) {
subscriptions.put(endpoint, new ArrayList<String>());
}
subscriptions.get(endpoint).add(topic);
}
if(IsValidTopic){
endpoint.subscribeAcknowledge(subscribe.messageId(), reasonCodes, MqttProperties.NO_PROPERTIES);
}
});
}
/*
* 处理退订
* */
private void UnSubscribeHandle(MqttEndpoint endpoint) {
endpoint.unsubscribeHandler(unsubscribe -> {
//遍历要退订的topic
for (String unsubscribedTopic : unsubscribe.topics()) {
topicSubscribers.get(unsubscribedTopic).remove(endpoint);
//如果某topic的订阅列表为空,删除topic
if (topicSubscribers.get(unsubscribedTopic).size() == 0) {
topicSubscribers.remove(unsubscribedTopic);
}
subscriptions.get(endpoint).remove(unsubscribedTopic);
//同上
if (subscriptions.get(endpoint).size()==0){
subscriptions.remove(endpoint);
}
System.out.println("unsubscribed :" + endpoint.auth().getUsername() + "for" + unsubscribedTopic);
}
endpoint.unsubscribeAcknowledge(unsubscribe.messageId());
});
}
private void ReceiveHandle(MqttEndpoint endpoint) {
endpoint.publishHandler(publish -> {
String topic = publish.topicName();
Buffer payload = publish.payload();
//对topic的合法性进行判断
if (!isValidTopic(topic)){
endpoint.publish(topic, Buffer.buffer("非法topic,topic不可包含空格"), MqttQoS.AT_MOST_ONCE, false, false);
return;
}
//记录日志接收到设备发布的消息
System.out.println("Received message [" + publish.payload().toString(Charset.defaultCharset()) + "] with QoS [" + publish.qosLevel() + "]");
if (publish.qosLevel() == MqttQoS.AT_LEAST_ONCE) {
endpoint.publishAcknowledge(publish.messageId());
} else if (publish.qosLevel() == MqttQoS.EXACTLY_ONCE) {
endpoint.publishReceived(publish.messageId());
}
//遍历订阅关系,进行消息发布
for (Map.Entry<String, List<MqttEndpoint>> entry : topicSubscribers.entrySet()) {
String subscribedTopic = entry.getKey();
//被订阅的topic
List<MqttEndpoint> subscribers = entry.getValue();
//订阅上方topic的订阅者
//判断消息发布的topic是否能和被设备订阅的topic按照规则匹配
if (isTopicMatch(subscribedTopic, topic)) {
//若匹配,则遍历topic订阅者列表,并进行消息发布
for (MqttEndpoint subscriber : subscribers) {
subscriber.publish(topic, payload, publish.qosLevel(), publish.isDup(), publish.isRetain());
}
}
}
});
endpoint.publishAcknowledgeHandler(messageId -> {
System.out.println("received ack for message =" + messageId);
}).publishReceivedHandler(messageId -> {
endpoint.publishRelease(messageId);
}).publishCompletionHandler(messageId -> {
System.out.println("Received ack for message =" + messageId);
});
endpoint.publishReleaseHandler(endpoint::publishComplete);
}
/*
* 判断topic是否匹配
* */
private boolean isTopicMatch(String subscribedTopic, String publishedTopic) {
String[] publishTopicArray = publishedTopic.split("/");
String[] subscribedTopicArray = subscribedTopic.split("/");
//将两个要比较的topic分割
//订阅的topic长度不能比发布的topic长一个以上
if (subscribedTopicArray.length - 1 > publishTopicArray.length) {
return false;
}
//如果发布的topic长度比订阅的topic长度要长
//并且订阅的topic最后不是以#结尾都返回false,因为这不可能
if (subscribedTopicArray.length<publishTopicArray.length){
if (!subscribedTopicArray[subscribedTopicArray.length-1].equals("#")){
return false;
}
}
//对两个topic进行比较
for (int i = 0; i < publishTopicArray.length && i < subscribedTopicArray.length; i++) {
//如果匹配成功或者匹配到了+,进行下一层匹配
if (subscribedTopicArray[i].equals(publishTopicArray[i])||subscribedTopicArray[i].equals("+")){
continue;
}
//如果匹配到了#,直接通过
if (subscribedTopicArray[i].equals("#")) {
break;
}
return false;
}
return true;
}
public boolean isValidTopic(String topic) {
//topic 不能包含任何空格,并且要么以 /# 结尾,要么不包含 #
return (!topic.matches(".*\\s+.*"))&&(topic.matches(".*(?:\\/#)?$"));
}
}