Mqtt協議-broker-moqutte在Springboot中的使用

什麼是MQTT協議

本博客源碼:https://github.com/xushuoAI/broker-moqutte-springboot

https://github.com/mcxiaoke/mqtt
這是一位大佬的中文翻譯,大家可以看看。

在SpringBoot中啓動moquette.broker.Server

https://segmentfault.com/a/1190000016456748
這是一位大佬的整合

客戶端連接到broker並使用

通過上面的操作,我們可以在啓動一個SpringBoot項目的同時啓動好moqutte.broker服務。現在我們只需要創建客戶端連接到broker服務就可以實現客戶端之間的通信了。下面這段代碼,大家肯定很熟悉了,網上基本都是這個版本,通過指定服務器的IP地址和端口,我們創建了一個客戶端連接到我們啓動的Broker服務。然後這裏使用的連接方式是回調式鏈接。

1.採用阻塞式的連接的(BlockingConnection)

2.採用回調式的連接 (CallbackConnection)

3.採用Future樣式的連接(FutureConnection)

https://blog.csdn.net/woaijianning/article/details/17417409參考文章

public class MQTTServer {
private static final Logger LOG = LoggerFactory.getLogger(MQTTServer.class);
    private final static String CONNECTION_STRING = "tcp://0.0.0.0:1883";
    private final static boolean CLEAN_START = true;
    private final static short KEEP_ALIVE = 30;// 低耗網絡,但是又需要及時獲取數據,心跳30s
    public  static Topic[] topics = {
            new Topic("china/beijing", QoS.EXACTLY_ONCE),
            new Topic("china/tianjin", QoS.AT_LEAST_ONCE),
            new Topic("china/henan", QoS.AT_MOST_ONCE)};
    public final  static long RECONNECTION_ATTEMPT_MAX=6;
    public final  static long RECONNECTION_DELAY=2000;

    public final static int SEND_BUFFER_SIZE=2*1024*1024;//發送最大緩衝爲2M

    public static void main(String arg[])   {
        MQTT mqtt = new MQTT();
        try {
            //設置服務端的ip
            mqtt.setHost(CONNECTION_STRING);
            //連接前清空會話信息
            mqtt.setCleanSession(CLEAN_START);
            //設置重新連接的次數
            mqtt.setReconnectAttemptsMax(RECONNECTION_ATTEMPT_MAX);
            //設置重連的間隔時間
            mqtt.setReconnectDelay(RECONNECTION_DELAY);
            //設置心跳時間
            mqtt.setKeepAlive(KEEP_ALIVE);
            //設置緩衝的大小
            mqtt.setSendBufferSize(SEND_BUFFER_SIZE);

           /* //創建連接
            BlockingConnection connection = mqtt.blockingConnection();
            //開始連接
            connection.connect();*/
            // 使用回調式API
            final CallbackConnection callbackConnection = mqtt
                    .callbackConnection();

            // 連接監聽
            callbackConnection.listener(new Listener() {

                // 接收訂閱話題發佈的消息
                @Override
                public void onPublish(UTF8Buffer topic, Buffer payload,
                                      Runnable onComplete) {
                    System.out.println("=============receive msg================" + new String(payload.toByteArray()));
                    onComplete.run();
                }

                // 連接失敗
                @Override
                public void onFailure(Throwable value) {
                    System.out.println("===========connect failure===========");
                    callbackConnection.disconnect(null);
                }

                // 連接斷開
                @Override
                public void onDisconnected() {
                    System.out.println("====mqtt disconnected=====");

                }

                // 連接成功
                @Override
                public void onConnected() {
                    System.out.println("====mqtt connected=====");

                }
            });

            // 連接
            callbackConnection.connect(new Callback<Void>() {

                // 連接失敗
                public void onFailure(Throwable value) {
                    System.out.println("============連接失敗:" + value.getLocalizedMessage() + "============");
                }

                // 連接成功
                public void onSuccess(Void v) {
                    // 訂閱主題
                    String topic="msg/normal";
                    String topic1="msg/disconnect";
                    Topic[] topics = { new Topic(topic, QoS.AT_LEAST_ONCE), new Topic(topic1, QoS.AT_LEAST_ONCE) };
                    callbackConnection.subscribe(topics, new Callback<byte[]>() {
                        // 訂閱主題成功
                        public void onSuccess(byte[] qoses) {
                            System.out.println("========訂閱成功=======");
                        }

                        // 訂閱主題失敗
                        public void onFailure(Throwable value) {
                            System.out.println("========訂閱失敗=======");
                            callbackConnection.disconnect(null);
                        }
                    });

                    // 發佈消息
                    callbackConnection.publish(topic, ("Hello,我是服務端").getBytes(), QoS.AT_LEAST_ONCE, true, new Callback<Void>() {

                        public void onSuccess(Void v) {
                            LOG.info("===========消息發佈成功============");
                        }

                        public void onFailure(Throwable value) {
                            LOG.info("========消息發佈失敗=======");
                            callbackConnection.disconnect(null);
                        }
                    });

                }
            });

            try {
                int count=0;
                //getClient();
                while(true){
                /*    count++;
                    //訂閱的主題
                    String topic="china/tianjin";
                    //主題的內容
                    String message="hello "+count+"chinese people !";

                    //connection.publish(topic, message.getBytes(), QoS.AT_LEAST_ONCE, false);
                    LOG.info("MQTTServer Message  Topic="+topic+"  Content :"+message);
                    Thread.sleep(2000);*/
                }
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        } catch (URISyntaxException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

通過上面的代碼,我們就可以簡單的創建和實現客戶端,通過向主題發送消息,其他訂閱了該主題的客戶端就可以收到對應的消息。

客戶端在線狀態

但是在使用過程中,我們肯定是想要知道客戶端的在線情況,簡單的發送消息已經實現了,那我們就來看看如何知道客戶端在線情況吧!

- 客戶端如何知道自己是否連接上服務端?

在上面的代碼中我們使用了回調式連接,在該連接方式中

 connection.listener(new Listener() {

            public void onDisconnected() {
                // Send a message to a topic
                LOG.info("----------連接掉線-------------");
           
            }
            public void onConnected() {
                LOG.info("----------連接上-------------");
            }

            public void onPublish(UTF8Buffer topic, Buffer payload, Runnable ack) {
                // You can now process a received message from a topic.
                // Once process execute the ack runnable.
                System.out.println("=============receive msg================" + new String(payload.toByteArray()));
                ack.run();
            }
            public void onFailure(Throwable value) {
                LOG.info("----------a connection failure occured.-------------");
                connection.disconnect(null); // a connection failure occured.
            }
        });

在該Listener中可以監聽到客戶端是否連接上了服務端。

- 服務端如何知道有客戶端掉線?

   mqttServer.startServer(config, interceptHandlers, null, iAuthenticatorImp, authorizatorPolicy);

在剛纔,我們在Springboot中啓動了服務端,在start方法中我們傳遞了幾個參數,其中interceptHandlers就可以實現對客戶端的監聽。

package com.hyls.sb.mqtt;

import groovy.util.logging.Slf4j;
import io.moquette.interception.AbstractInterceptHandler;
import io.moquette.interception.messages.InterceptAcknowledgedMessage;
import io.moquette.interception.messages.InterceptConnectMessage;
import io.moquette.interception.messages.InterceptConnectionLostMessage;
import io.moquette.interception.messages.InterceptPublishMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Slf4j
@Component("safetyInterceptHandler")
public class SafetyInterceptHandler extends AbstractInterceptHandler {
    private static final Logger LOG = LoggerFactory.getLogger(MoquetteServer.class);
    @Override
    public String getID() {
        return SafetyInterceptHandler.class.getName();
    }

    @Override
    public void onConnect(InterceptConnectMessage msg) {
        super.onConnect(msg);
        LOG.info("==啥玩意連接上了====");
    }

    @Override
    public void onConnectionLost(InterceptConnectionLostMessage msg) {
        super.onConnectionLost(msg);

        LOG.info("==哦豁,有人掉了ID:===="+msg.getClientID());
        LOG.info("==哦豁,有人掉了Name:===="+msg.getUsername());
    }


    @Override
    public void onPublish(InterceptPublishMessage msg) {
        super.onPublish(msg);
    }


    @Override
    public void onMessageAcknowledged(InterceptAcknowledgedMessage msg) {
        super.onMessageAcknowledged(msg);
    }

}

我們自定義一個類,實現對應的監聽方法。就可以知道有客戶端掉線了。那是如何判斷掉線呢。剛纔我們新建客戶端的時候設置了心跳這一值,該值就是服務端判斷客戶端有沒有掉線的依據,超過心跳時間沒有收到客戶端發送的數據,服務端就認爲客戶端已掉線。(這裏的代碼,在上面的那個鏈接中。)

知道有客戶端掉了,是誰掉了?

在實際應用中,我們知道有客戶端掉了,但是隻有一個clientId,這個ID不是我們設置的,知道這個ID我們也不知道是那一個客戶端掉了。

package io.moquette.interception.messages;

public class InterceptConnectionLostMessage implements InterceptMessage {
    private final String clientID;
    private final String username;

    public InterceptConnectionLostMessage(String clientID, String username) {
        this.clientID = clientID;
        this.username = username;
    }

    public String getClientID() {
        return this.clientID;
    }

    public String getUsername() {
        return this.username;
    }
}

查看moquette.messages源碼,如上,我們發現有一個userName屬性,誒,我們就可以通過設置這個userName就知道是誰掉了澀。
在上面新建客戶端的時候我們

 		mqtt.setHost(CONNECTION_STRING);
        //連接前清空會話信息
        mqtt.setCleanSession(CLEAN_START);
        //設置重新連接的次數
        mqtt.setReconnectAttemptsMax(RECONNECTION_ATTEMPT_MAX);
        //設置重連的間隔時間
        mqtt.setReconnectDelay(RECONNECTION_DELAY);
        //設置心跳時間
        mqtt.setKeepAlive(KEEP_ALIVE);

set了一堆東西,那有沒有setName方法呢。
哎呀,果然有該方法

     mqtt.setUserName("王德發");

這裏我們新增設置一個name,叫王德發(嘿嘿 滑稽)。
這下我們就知道是誰掉了。趕緊試試
就在我們翹着二郎腿,美滋滋的時候一運行

LOG.error("Authenticator has rejected the MQTT credentials CId={}, username={}", clientId, login);

控制檯就出現這個玩意,一看出現了權限問題。哦豁,咋搞
原來我們設置了運行匿名訪問服務端,所有之前設置的客戶端可以匿名訪問,現在我們設置了一個username,Mqtt就走了鑑權,但是我們沒有設置對應的鑑權規則。查看鑑權源碼

    private boolean login(MqttConnectMessage msg, String clientId) {
        if (msg.variableHeader().hasUserName()) {
            byte[] pwd = null;
            if (msg.variableHeader().hasPassword()) {
                pwd = msg.payload().password().getBytes(StandardCharsets.UTF_8);
            } else if (!this.brokerConfig.isAllowAnonymous()) {
                LOG.error("Client didn't supply any password and MQTT anonymous mode is disabled CId={}", clientId);
                return false;
            }

            String login = msg.payload().userName();
            if (!this.authenticator.checkValid(clientId, login, pwd)) {
                LOG.error("Authenticator has rejected the MQTT credentials CId={}, username={}", clientId, login);
                return false;
            }

            NettyUtils.userName(this.channel, login);
        } else if (!this.brokerConfig.isAllowAnonymous()) {
            LOG.error("Client didn't supply any credentials and MQTT anonymous mode is disabled. CId={}", clientId);
            return false;
        }

        return true;
    }

這裏進行check
public interface IAuthenticator {
    boolean checkValid(String var1, String var2, byte[] var3);
}

哎呀,那我們如何去自定義鑑權規則呢
回到啓動Broker的這個方法,發現參數中有一個 IAuthenticator authenticator

        mqttServer.startServer(config, interceptHandlers, null, iAuthenticatorImp, authorizatorPolicy);

喔,原來這裏就是我們可以自定義傳入鑑權規則

import io.moquette.broker.security.IAuthenticator;
import org.springframework.stereotype.Component;


@Component
public class IAuthenticatorImp implements IAuthenticator {


    @Override
    public boolean checkValid(String s, String s1, byte[] bytes) {
        return true;
    }
}

我們實現鑑權接口,然後把自定義實現類作爲參數傳遞到啓動方法中。
果然現在由於我們直接返回了true,我們的客戶端設置了用戶名可以直接連接上,這樣有客戶端掉線。我們就可以通過自定義的userName知道是誰掉線了。
博主也是新手,有誤希望大家指點~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章