vert.x实现MQTT Server的订阅、退订、接收消息、断开链接、遗嘱消息等功能

直接上代码,使用的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(".*(?:\\/#)?$"));
    }
}

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值