搭建EMQX,java实现MQTT协议

搭建EMQX

实现MQTT协议,broker地址。附带JAVA案例

EMQX 是全球最具可扩展性的开源 MQTT 代理,高性能连接 100M+ 物联网设备,同时保持每秒 1M 消息吞吐量和亚毫秒级延迟。

开原地址 (github.com)

EMQX docker官方仓库

MQTT协议中文文档

安装EMQX

docker run -d --restart=always --name emqx -p 18083:18083 -p 1883:1883 emqx:latest

安装完成后,打开地址:http://192.168.1.95:18083/ (我本地是192.168.1.95)

默认的登录名是 admin 密码是 public,第一次会提示修改密码,进入网页后,右上角setting 可以设置简体中文。

界面:

在这里插入图片描述
在这里插入图片描述

最后再代码中实现时候配置。

String broker = "tcp://192.168.1.95:1883";

java实现:

maven工程:

    <dependency>
      <groupId>org.eclipse.paho</groupId>
      <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
      <version>1.2.5</version>
    </dependency>

gradle工程:

implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5'

消息发布者

package mqtt;

import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;

import java.nio.charset.Charset;

/**
 * JAVA实现MQTT消息发布者
 */
public class MqttTest1 {
    
    
    public static void main(String[] args) {
    
    
        String topic = "test2";
        int qos = 1;
        String broker = "tcp://192.168.1.95:1883";
        String userName = "yujing";
        String password = "123456";
        String clientId = "pubClient";
        // 内存存储
        MemoryPersistence persistence = new MemoryPersistence();
        try {
    
    
            // 创建客户端
            MqttClient sampleClient = new MqttClient(broker, clientId, persistence);
            // 创建链接参数
            MqttConnectOptions connOpts = new MqttConnectOptions();
            // 在重新启动和重新连接时记住状态
            connOpts.setCleanSession(false);
            // 设置连接的用户名
            connOpts.setUserName(userName);
            connOpts.setPassword(password.toCharArray());
            connOpts.setConnectionTimeout(30);
            // 建立连接
            System.out.println("连接到 broker: " + broker);
            sampleClient.connect(connOpts);
            System.out.println("连接成功.");

            int i=1;
            while (true){
    
    
                String s="哈哈,第"+(i++)+"次发送消息。";
                // 创建消息
                MqttMessage mmg=new MqttMessage(s.getBytes(Charset.defaultCharset()));
                // 设置消息的服务质量
                mmg.setQos(qos);
                // 发布消息
                System.out.println("向" + topic + "发送消息:" + s);
                sampleClient.publish(topic, mmg);
                Thread.sleep(3000);
            }
//            // 断开连接
//            sampleClient.disconnect();
//            // 关闭客户端
//            sampleClient.close();
        } catch (MqttException me) {
    
    
            System.out.println("reason " + me.getReasonCode());
            System.out.println("msg " + me.getMessage());
            System.out.println("loc " + me.getLocalizedMessage());
            System.out.println("cause " + me.getCause());
            System.out.println("excep " + me);
            me.printStackTrace();
        } catch (InterruptedException e) {
    
    
            throw new RuntimeException(e);
        }
    }
}

消息订阅者

package mqtt;

import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;

/**
 * java实现MQTT,订阅者
 */
public class MqttTest2 {
    
    
    public static void main(String[] args) {
    
    
        //EMQ X 默认端口 1883
        String broker = "tcp://192.168.1.95:1883";
        String TOPIC = "test2";
        int qos = 1;
        String userName = "yujing";
        String passWord = "123456";

        String clientId = "subClient";
        try {
    
    
            // MQTT的连接设置
            MqttConnectOptions options = new MqttConnectOptions();
            // 设置是否清空session,这里如果设置为false表示服务器会保留客户端的连接记录,这里设置为true表示每次连接到服务器都以新的身份连接
            options.setCleanSession(true);
            // 设置连接的用户名
            options.setUserName(userName);
            // 设置连接的密码
            options.setPassword(passWord.toCharArray());
            // 设置超时时间 单位为秒
            options.setConnectionTimeout(10);
            // 设置会话心跳时间 单位为秒 服务器会每隔1.5*20秒的时间向客户端发送个消息判断客户端是否在线,但这个方法并没有重连的机制
            options.setKeepAliveInterval(20);
            // host为主机名,test为clientId即连接MQTT的客户端ID,一般以客户端唯一标识符表示,MemoryPersistence设置clientId的保存形式,默认为以内存保存
            MqttClient client = new MqttClient(broker, clientId, new MemoryPersistence());
            // 设置回调函数
            client.setCallback(new MqttCallback() {
    
    
                public void connectionLost(Throwable cause) {
    
    
                    System.out.println("connectionLost");
                }
                public void messageArrived(String topic, MqttMessage message) {
    
    
                    System.out.println("======监听到来自[" + topic + "]的消息======");
                    System.out.println("内容:"+new String(message.getPayload()));
                }
                public void deliveryComplete(IMqttDeliveryToken token) {
    
    
                    System.out.println("deliveryComplete---------"+ token.isComplete());
                }
            });
            // 建立连接
            System.out.println("连接到 broker: " + broker);
            client.connect(options);
            System.out.println("连接成功.");
            //订阅消息
            client.subscribe(TOPIC, qos);
            System.out.println("开始监听" + TOPIC);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

运行效果:

在这里插入图片描述
在这里插入图片描述

封装后使用

import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeoutException;

/**
 * mqtt协议,服务器端实现
 * 当连接不上MQTT服务器,会每隔一定时间自动重新连接。
 * 由于对于mqtt协议来说,本来是没有服务器概念,大家都是客户端,每个客户端订阅一个TOPIC(主题),只要其他客户端向这个TOPIC发送数据,所有订阅了该TOPIC的设备都能收到。
 * 官方文档:<a href="https://www.emqx.com/zh/mqtt">快速入门</a>
 * 官方文档:<a href="https://www.emqx.io/docs/zh/v5.0/">详细文档</a>
 *
 * @author yujing 2023年3月28日10:20:45
 */
/*
使用举例:

//订阅了三个主题server,offline,online
String id = "server/" + UUID.randomUUID().toString().replace("-", "").substring(18);
MQTT mqtt = new MQTT("tcp://192.168.1.95:1883", id, new String[]{"server", "offline", "online"});
mqtt.setMessageListener(this);
mqtt.init();

//发送消息
mqtt.send(topic, msg);

//接收消息
@Override
public void messageArrived(String topic, MqttMessage message) {
   println("收到消息" + message.toString());
    switch (topic) {
        case "offline" ->println("有设备掉线了:" + message);
        case "online" ->println("有设备上线了:" + message);
        case "server" -> {
            String json = message.toString();
            //解析消息
            try {
                int command = mapper.readTree(json).get("command").asInt();
                //指令分发
                switch (command) {
                    case 1 -> command1(json);
                    case 2 -> command2(json);
                    case 3 -> command3(json);
                    case 4 -> command4(json);
                    case 5 -> command5(json);
                    case 6 -> command6(json);
                }
            } catch (JsonProcessingException e) {
                System.err.println("json解析失败");
                e.printStackTrace();
            }
        }
    }
}
 */
public class MQTT {
    
    
    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MQTT.class);

    //EMQX默认端口1883举例:tcp://192.168.1.95:1883
    String broker = "";
    int qos = 2; //发送消息时候的qos,0:至多一次,1:至少一次,2:确保只有一次。
    String userName = ""; //用户名,可以空
    String passWord = ""; //用户密码,可以空
    String clientId = ""; //自己唯一id
    //要订阅的主题些
    String[] subscribeTopics; // = new String[]{"server", "offline", "online"};
    int[] subscribeQoSs; // = new int[]{qos, qos, qos};
    String willTopic = "offline"; //遗嘱主题,当自己掉线时,会向这个主题通知消息,暂定消息内容为自己id
    /**
     * MQTT的连接设置
     */
    MqttConnectOptions options;
    /**
     * MQTT的客户端,不采用MqttClient是因为MqttAsyncClient才能知道每一步状态成功与否
     */
    MqttAsyncClient client;
    /**
     * 消息监听
     */
    MessageListener messageListener;

    //一个线程的线程池
    ExecutorService executorService;

    /**
     * @param broker   mqtt服务器地址
     * @param clientId 自己的唯一id
     */
    public MQTT(String broker, String clientId, String topic) {
    
    
        this(broker, clientId, new String[]{
    
    topic}, null);
    }

    public MQTT(String broker, String clientId, String[] topics) {
    
    
        this(broker, clientId, topics, null);
    }

    public MQTT(String broker, String clientId, String[] topics, int[] qoSs) {
    
    
        this.broker = broker;
        this.clientId = clientId;
        this.subscribeTopics = topics;
        this.subscribeQoSs = qoSs;
        if (qoSs == null) {
    
    
            this.subscribeQoSs = new int[topics.length];
            Arrays.fill(this.subscribeQoSs, qos);
        }
        executorService = Executors.newSingleThreadExecutor();
    }

    /**
     * 初始化
     */
    public void init() {
    
    
        try {
    
    
            create();
            connect();
        } catch (MqttException | TimeoutException e) {
    
    
            e.printStackTrace();
        }
    }

    private void create() throws MqttException {
    
    
        // MQTT的连接设置
        options = new MqttConnectOptions();
        // 设置是否清空session,这里如果设置为false表示服务器会保留客户端的连接记录,这里设置为true表示每次连接到服务器都以新的身份连接
        options.setCleanSession(true);
        // 设置连接的用户名
        options.setUserName(userName);
        // 设置连接的密码
        options.setPassword(passWord.toCharArray());
        // 设置连接超时
        options.setConnectionTimeout(15);
        // 设置超时时间 单位为秒
        options.setConnectionTimeout(10);
        // 设置会话心跳时间 单位为秒 服务器会每隔1.5*20秒的时间向客户端发送个消息判断客户端是否在线,但这个方法并没有重连的机制
        options.setKeepAliveInterval(20);
        //遗嘱消息,保留消息
        options.setWill(willTopic, clientId.getBytes(), 1, true);
        // host为主机名,test为clientId即连接MQTT的客户端ID,一般以客户端唯一标识符表示,MemoryPersistence设置clientId的保存形式,默认为以内存保存
        client = new MqttAsyncClient(broker, clientId, new MemoryPersistence());
        MqttCallback callback = new MqttCallback() {
    
    
            public void connectionLost(Throwable cause) {
    
    
                println("MQTT连接断开");
                try {
    
    
                    connect();
                } catch (MqttException | TimeoutException e) {
    
    
                    println("MQTT自动重连失败" + e.getMessage());
                }
            }

            public void messageArrived(String topic, MqttMessage message) {
    
    
                //System.out.println("MQTT接收消息内容 : " + message);
                if (messageListener != null) {
    
    
                    //为什么要开线程?1.如果在这儿有耗时操作,那么就暂时收不到之后的数据。2.如果这儿有发送操作,发送里面有阻塞,那么发可能会发生死锁。因为messageArrived执行完毕后,才会触发发送是否成功的回调。
                    //为什么要线程池,且数量为1,因为上面第二个问题虽然可以开线程解决,但是同时收到多条消息时,由于处理时间不一致,可能导致队列顺序混乱。
                    executorService.execute(() -> {
    
    
                        try {
    
    
                            messageListener.messageArrived(topic, message);
                        } catch (Exception e) {
    
    
                            e.printStackTrace();
                        }
                    });
                }
            }

            public void deliveryComplete(IMqttDeliveryToken token) {
    
    
                //System.out.println("发送状态:" + token.isComplete() + ",消息id:" + token.getMessageId());
            }
        };
        // 设置回调函数
        client.setCallback(callback);
    }

    /**
     * 连接
     */
    private boolean connect() throws MqttException, TimeoutException {
    
    
        // 建立连接
        ActionListener listener = new ActionListener("MQTT连接", 1000 * 30);
        client.connect(options, "Connect context", listener);
        listener.lock();
        //如果连接失败,自动重连
        if (listener.isSuccess()) {
    
    
            subscribe();
        } else {
    
    
            connect();
        }
        return listener.isSuccess();
    }

    /**
     * 订阅
     */
    private boolean subscribe() throws MqttException, TimeoutException {
    
    
        ActionListener listener = new ActionListener("MQTT订阅", 1000 * 20);
        //订阅主题
        client.subscribe(subscribeTopics, subscribeQoSs, "Subscribe context", listener);
        listener.lock();
        if (listener.success) {
    
    
            //通知在线了,保留消息
            client.publish("online", clientId.getBytes(), 1, true);
        }
        return listener.isSuccess();
    }

    /**
     * 发送消息
     */
    //举例:send("recycling",msg)
    public boolean send(String topic, String msg) {
    
    
        try {
    
    
            // 创建消息
            MqttMessage message = new MqttMessage(msg.getBytes(Charset.defaultCharset()));
            // 设置消息的服务质量
            message.setQos(qos);
            ActionListener listener = new ActionListener("MQTT发送", 1000 * 20);
            client.publish(topic, message, null, listener);
            listener.lock();
            return listener.isSuccess();
        } catch (MqttException | TimeoutException e) {
    
    
            println(e.getMessage());
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 断开连接并且关闭客户端
     */
    public void disconnectAndClose() {
    
    
        try {
    
    
            // 断开连接
            client.disconnect();
        } catch (MqttException e) {
    
    
            e.printStackTrace();
        }
        // 关闭客户端
        try {
    
    
            client.close();
        } catch (MqttException e) {
    
    
            e.printStackTrace();
        }
        broker = "";
        userName = "";
        passWord = "";
        clientId = "";
        options = null;
        client = null;
        messageListener = null;
        executorService.shutdown();
    }

    private static void println(String str) {
    
    
        //System.out.println(str);
        log.info(str);
    }

    public String getBroker() {
    
    
        return broker;
    }

    public void setBroker(String broker) {
    
    
        this.broker = broker;
    }

    public int getQos() {
    
    
        return qos;
    }

    public void setQos(int qos) {
    
    
        this.qos = qos;
    }

    public String getUserName() {
    
    
        return userName;
    }

    public void setUserName(String userName) {
    
    
        this.userName = userName;
    }

    public String getPassWord() {
    
    
        return passWord;
    }

    public void setPassWord(String passWord) {
    
    
        this.passWord = passWord;
    }

    public String getClientId() {
    
    
        return clientId;
    }

    public void setClientId(String clientId) {
    
    
        this.clientId = clientId;
    }

    public MqttConnectOptions getOptions() {
    
    
        return options;
    }

    public void setOptions(MqttConnectOptions options) {
    
    
        this.options = options;
    }

    public MqttAsyncClient getClient() {
    
    
        return client;
    }

    public void setClient(MqttAsyncClient client) {
    
    
        this.client = client;
    }

    public MessageListener getMessageListener() {
    
    
        return messageListener;
    }

    public void setMessageListener(MessageListener messageListener) {
    
    
        this.messageListener = messageListener;
    }

    public String[] getSubscribeTopics() {
    
    
        return subscribeTopics;
    }

    public void setSubscribeTopics(String[] subscribeTopics) {
    
    
        this.subscribeTopics = subscribeTopics;
    }

    public int[] getSubscribeQoSs() {
    
    
        return subscribeQoSs;
    }

    public void setSubscribeQoSs(int[] subscribeQoSs) {
    
    
        this.subscribeQoSs = subscribeQoSs;
    }

    public String getWillTopic() {
    
    
        return willTopic;
    }

    public void setWillTopic(String willTopic) {
    
    
        this.willTopic = willTopic;
    }

    //-----------------------------------------------------内部类-----------------------------------------------------

    /**
     * 监听连接,订阅,发送状态,异步转同步
     *
     * @author yujing 2023年3月28日16:22:26
     */
    static class ActionListener implements IMqttActionListener {
    
    
        /**
         * 监听器名称
         */
        String name;
        /**
         * 是否成功
         */
        boolean success;
        /**
         * 超时时间
         */
        int timeOut = 5000;
        /**
         * 锁
         */
        final Object waiter = new Object();

        public ActionListener(String name) {
    
    
            this.name = name;
        }

        public ActionListener(String name, int timeOut) {
    
    
            this.name = name;
            this.timeOut = timeOut;
        }

        public String getName() {
    
    
            return name;
        }

        public void setName(String name) {
    
    
            this.name = name;
        }

        public boolean isSuccess() {
    
    
            return success;
        }

        public void setSuccess(boolean success) {
    
    
            this.success = success;
        }

        @Override
        public void onSuccess(IMqttToken iMqttToken) {
    
    
            println(name + "成功");
            success = true;
            unLock();
        }

        @Override
        public void onFailure(IMqttToken iMqttToken, Throwable throwable) {
    
    
            println(name + "失败");
            success = false;
            unLock();
        }


        //异步转同步,加锁
        public void lock() throws TimeoutException {
    
    
            synchronized (waiter) {
    
    
                try {
    
    
                    waiter.wait(timeOut);
                } catch (InterruptedException e) {
    
    
                    println("超时");
                    e.printStackTrace();
                    throw new TimeoutException("超时");
                }
            }
        }

        //异步转同步,解锁
        public void unLock() {
    
    
            synchronized (waiter) {
    
    
                waiter.notifyAll();
            }
        }
    }

    //-----------------------------------------------------监听器-----------------------------------------------------

    /**
     * 监听消息
     */
    interface MessageListener {
    
    
        void messageArrived(String topic, MqttMessage message) throws Exception;
    }
}
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Arrays;
import java.util.UUID;

public class MqttService {
    
    
    static ObjectMapper mapper = new ObjectMapper();
    public static void main(String[] args) throws InterruptedException, JsonProcessingException {
    
    
        String id = "server/" + UUID.randomUUID().toString().replace("-", "").substring(18);
        MQTT mqtt = new MQTT("tcp://192.168.1.95:1883", id, new String[]{
    
    "server", "offline", "online"});

        mqtt.setMessageListener((topic, message) -> {
    
    
            System.out.println("收到消息,主题:" + topic + "  内容:" + message.toString());
            switch (topic) {
    
    
                case "offline" -> System.out.println("有设备掉线了:" + message);
                case "online" -> System.out.println("有设备上线了:" + message);
                case "server" -> {
    
    
                    String json = message.toString();

                }
            }
        });
        mqtt.init();

        var mMsg = new MqttMsg<>(1, Arrays.asList("哈哈哈", "嘿嘿嘿"));
        String json = mapper.writeValueAsString(mMsg);

        Thread.sleep(5000);
        mqtt.send("client/01", json);

        Thread.sleep(5000);
        mqtt.send("client/02", json);
    }
}


import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Arrays;
public class MqttTest1 {
    
    
    static ObjectMapper mapper = new ObjectMapper();

    public static void main(String[] args) throws InterruptedException {
    
    
        String id = "client/01";//"client/" + UUID.randomUUID().toString().replace("-", "").substring(18);
        MQTT mqtt = new MQTT("tcp://192.168.1.95:1883", id, "client/01");
        mqtt.setMessageListener((topic, message) -> {
    
    
            System.out.println("收到消息,主题:" + topic + "  内容:" + message.toString());

            var mMsg = new MqttMsg<>(1, Arrays.asList("999999", "00000000000", 333, 123.45));
            String json = mapper.writeValueAsString(mMsg);
            //回复
            mqtt.send("server", json);
        });
        mqtt.init();
    }
}

运行结果 client/01

[2023-04-08 17:19:02 下午]:INFO mqtt.MQTT.println(MQTT.java:261)MQTT连接成功
[2023-04-08 17:19:02 下午]:INFO mqtt.MQTT.println(MQTT.java:261)MQTT订阅成功
收到消息,主题:client/01内容:{"command":1,"data":["哈哈哈","嘿嘿嘿"]}
[2023-04-08 17:19:12 下午]:INFO mqtt.MQTT.println(MQTT.java:261)MQTT发送成功

运行结果 server

[2023-04-08 17:19:07 下午]:INFO mqtt.MQTT.println(MQTT.java:261)MQTT连接成功
[2023-04-08 17:19:07 下午]:INFO mqtt.MQTT.println(MQTT.java:261)MQTT订阅成功
收到消息,主题:online  内容:client/01
有设备上线了:client/01
收到消息,主题:online  内容:server/34a6d98563b424
有设备上线了:server/34a6d98563b424
[2023-04-08 17:19:12 下午]:INFO mqtt.MQTT.println(MQTT.java:261)MQTT发送成功
收到消息,主题:server内容:{"command":1,"data":["999999","00000000000",333,123.45]}
[2023-04-08 17:19:17 下午]:INFO mqtt.MQTT.println(MQTT.java:261)MQTT发送成功

猜你喜欢

转载自blog.csdn.net/Yu1441/article/details/129526406