物联网之对接MQTT最佳实践

小伙伴们,你们好呀,我是老寇,跟我一起学习对接MQTT

安装EMQX

采用docker-compose一键式,启动!!!

还没有安装docker朋友,参考文章下面两篇文章

# Ubuntu20.04安装Docker

# Centos7安装Docker 23.0.6

使用 emqx 5.4.1,按照老夫的教程来,请不要改版本号!!!
使用 emqx 5.4.1,按照老夫的教程来,请不要改版本号!!!
使用 emqx 5.4.1,按照老夫的教程来,请不要改版本号!!!
yml 复制代码
services:
    emqx:
      image: emqx/emqx:5.4.1
      container_name: emqx
      # 保持容器在没有守护程序的情况下运行
      tty: true
      restart: always
      privileged: true
      ports:
        - "1883:1883"
        - "8083:8083"
        - "8883:8883"
        - "18083:18083"
      environment:
        - TZ=Asia/Shanghai
      volumes:
        # 挂载数据存储
        - ./emqx/data:/opt/emqx/data
        # 挂载日志文件
        - ./emqx/log:/opt/emqx/log
      networks:
        - laokou_network
networks:
  laokou_network:
    driver: bridge

访问 http://127.0.0.1:18083 设置密码

EMQX MQTT【摘抄自官方文档】

EMQX官方文档

MQTT 是物联网 (IoT) 的 OASIS 标准消息传递协议。它被设计为一种极轻量的发布/订阅消息传输协议,非常适合以较小的代码占用空间和极低的网络带宽连接远程设备。MQTT 目前广泛应用于汽车、制造、电信、石油和天然气等众多行业。

EMQX 完全兼容 MQTT 5.0 和 3.x,本节将介绍 MQTT 相关功能的基本配置项,包括基本 MQTT 设置、订阅设置、会话设置、强制关闭设置和强制垃圾回收设置等

客户端对接

本文章采用三种客户端对接

维度 Paho Hivemq-MQTT-Client Vert.x MQTT Client
协议支持 MQTT 3.1.1(5.0 实验性) MQTT 5.0 完整支持 MQTT 5.0(较新版本)
性能 中(同步模式) 高(异步非阻塞) 极高(响应式架构)
依赖复杂度 中(仅 Netty) 高(需 Vert.x 生态)
社区资源 丰富 较少 中等
适用场景 传统 IoT、跨语言项目 企业级 MQTT 5.0、高吞吐 响应式系统、高并发微服务

Paho【不推荐,连接不稳定】

Paho代码地址

引入依赖
xml 复制代码
<dependencies>
    <dependency>
        <groupId>org.eclipse.paho</groupId>
        <artifactId>org.eclipse.paho.mqttv5.client</artifactId>
        <version>1.2.5</version>
    </dependency>
    <dependency>
        <groupId>org.eclipse.paho</groupId>
        <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
        <version>1.2.5</version>
    </dependency>
</dependencies>
项目集成

PahoProperties

java 复制代码
/**
 * @author laokou
 */
@Data
public class PahoProperties {

    private boolean auth = true;

    private String username = "emqx";

    private String password = "laokou123";

    private String host = "127.0.0.1";

    private int port = 1883;

    private String clientId;

    private int subscribeQos = 1;

    private int publishQos = 0;

    private int willQos = 1;

    private int connectionTimeout = 60;

    private boolean manualAcks = false;

    // @formatter:off
    /**
     * 控制是否创建新会话(true=新建,false=复用历史会话). clearStart=true => Broker 会在连接断开后立即清除所有会话信息.
     * clearStart=false => Broker 会在连接断开后保存会话信息,并在重新连接后复用会话信息.
     * <a href="https://github.com/hivemq/hivemq-mqtt-client/issues/627">...</a>
     */
    // @formatter:on
    private boolean clearStart = false;

    private int receiveMaximum = 10000;

    private int maximumPacketSize = 10000;

    // @formatter:off
    /**
     * 默认会话保留一天.
     * 最大值,4294967295L,会话过期时间【永不过期,单位秒】.
     * 定义客户端断开后会话保留的时间(仅在 Clean Session = false 时生效).
     */
    private long sessionExpiryInterval = 86400L;
    // @formatter:on

    /**
     * 心跳包每隔60秒发一次.
     */
    private int keepAliveInterval = 60;

    private boolean automaticReconnect = true;

    private Set<String> topics = new HashSet<>(0);

}

PahoMqttClientMessageCallbackV5

java 复制代码
/**
 * @author laokou
 */
@Slf4j
@RequiredArgsConstructor
public class PahoMqttClientMessageCallbackV5 implements MqttCallback {

    private final List<MessageHandler> messageHandlers;

    @Override
    public void disconnected(MqttDisconnectResponse disconnectResponse) {
       log.error("【Paho-V5】 => MQTT关闭连接");
    }

    @Override
    public void mqttErrorOccurred(MqttException ex) {
       log.error("【Paho-V5】 => MQTT报错,错误信息:{}", ex.getMessage());
    }

    @Override
    public void messageArrived(String topic, MqttMessage message) {
       for (MessageHandler messageHandler : messageHandlers) {
          if (messageHandler.isSubscribe(topic)) {
             log.info("【Paho-V5】 => MQTT接收到消息,Topic:{}", topic);
             messageHandler.handle(new org.laokou.sample.mqtt.handler.MqttMessage(message.getPayload(), topic));
          }
       }
    }

    @Override
    public void deliveryComplete(IMqttToken token) {
       log.info("【Paho-V5】 => MQTT消息发送成功,消息ID:{}", token.getMessageId());
    }

    @Override
    public void connectComplete(boolean reconnect, String uri) {
       if (reconnect) {
          log.info("【Paho-V5】 => MQTT重连成功,URI:{}", uri);
       }
       else {
          log.info("【Paho-V5】 => MQTT建立连接,URI:{}", uri);
       }
    }

    @Override
    public void authPacketArrived(int reasonCode, MqttProperties properties) {
       log.info("【Paho-V5】 => 接收到身份验证数据包:{}", reasonCode);
    }

}

PahoV5MqttClientTest

java 复制代码
/**
 * @author laokou
 */
@SpringBootTest
@RequiredArgsConstructor
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
class PahoV5MqttClientTest {

    private final List<MessageHandler> messageHandlers;

    @Test
    void testMqttClient() throws InterruptedException {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(16);

        PahoProperties pahoProperties = new PahoProperties();
        pahoProperties.setClientId("test-client-3");
        pahoProperties.setTopics(Set.of("/test-topic-3/#"));
        PahoMqttClientV5 pahoMqttClientV5 = new PahoMqttClientV5(pahoProperties, messageHandlers, scheduledExecutorService);
        pahoMqttClientV5.open();
        Thread.sleep(1000);
        pahoMqttClientV5.publish("/test-topic-3/789", "Hello World789".getBytes());
    }

}

PahoMqttClientMessageCallbackV3

java 复制代码
/**
 * @author laokou
 */
@Slf4j
@RequiredArgsConstructor
public class PahoMqttClientMessageCallbackV3 implements MqttCallback {

    private final List<MessageHandler> messageHandlers;

    @Override
    public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
       log.info("【Paho-V3】 => MQTT消息发送成功,消息ID:{}", iMqttDeliveryToken.getMessageId());
    }

    @Override
    public void connectionLost(Throwable throwable) {
       log.error("【Paho-V3】 => MQTT关闭连接");
    }

    @Override
    public void messageArrived(String topic, MqttMessage message) throws Exception {
       for (MessageHandler messageHandler : messageHandlers) {
          if (messageHandler.isSubscribe(topic)) {
             log.info("【Paho-V3】 => MQTT接收到消息,Topic:{}", topic);
             messageHandler.handle(new org.laokou.sample.mqtt.handler.MqttMessage(message.getPayload(), topic));
          }
       }
    }
}

PahoV3MqttClientTest

java 复制代码
/**
 * @author laokou
 */
@SpringBootTest
@RequiredArgsConstructor
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
class PahoV3MqttClientTest {

    private final List<MessageHandler> messageHandlers;

    @Test
    void testMqttClient() throws InterruptedException {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(16);

        PahoProperties pahoProperties2 = new PahoProperties();
        pahoProperties2.setClientId("test-client-4");
        pahoProperties2.setTopics(Set.of("/test-topic-4/#"));
        PahoMqttClientV3 pahoMqttClientV3 = new PahoMqttClientV3(pahoProperties2, messageHandlers, scheduledExecutorService);
        pahoMqttClientV3.open();
        Thread.sleep(1000);
        pahoMqttClientV3.publish("/test-topic-4/000", "Hello World000".getBytes());
    }

}

Hivemq-MQTT-Client【不推荐】

注意:订阅一段时间收不到数据,标准mqtt5.0协议,不兼容emqx broker mqtt5.0

Hivemq代码地址

引入依赖
xml 复制代码
<dependencies>
    <dependency>
        <groupId>com.hivemq</groupId>
        <artifactId>hivemq-mqtt-client-reactor</artifactId>
        <version>1.3.5</version>
    </dependency>
    <dependency>
        <groupId>com.hivemq</groupId>
        <artifactId>hivemq-mqtt-client-epoll</artifactId>
        <version>1.3.5</version>
        <type>pom</type>
    </dependency>
<dependencies>
项目集成

HivemqProperties

java 复制代码
/**
 * @author laokou
 */
@Data
public class HivemqProperties {

    private boolean auth = true;

    private String username = "emqx";

    private String password = "laokou123";

    private String host = "127.0.0.1";

    private int port = 1883;

    private String clientId;

    private int subscribeQos = 1;

    private int publishQos = 0;

    private int willQos = 1;

    // @formatter:off
    /**
     * 控制是否创建新会话(true=新建,false=复用历史会话). clearStart=true => Broker 会在连接断开后立即清除所有会话信息.
     * clearStart=false => Broker 会在连接断开后保存会话信息,并在重新连接后复用会话信息.
     * <a href="https://github.com/hivemq/hivemq-mqtt-client/issues/627">...</a>
     */
    // @formatter:on
    private boolean clearStart = false;

    private int receiveMaximum = 10000;

    private int sendMaximum = 10000;

    private int maximumPacketSize = 10000;

    private int sendMaximumPacketSize = 10000;

    private int topicAliasMaximum = 1024;

    private int sendTopicAliasMaximum = 2048;

    private long messageExpiryInterval = 86400L;

    private boolean requestProblemInformation = true;

    private boolean requestResponseInformation = true;

    // @formatter:off
    /**
     * 默认会话保留一天.
     * 最大值,4294967295L,会话过期时间【永不过期,单位秒】.
     * 定义客户端断开后会话保留的时间(仅在 Clean Session = false 时生效).
     */
    private long sessionExpiryInterval = 86400L;
    // @formatter:on

    /**
     * 心跳包每隔60秒发一次.
     */
    private int keepAliveInterval = 60;

    private boolean automaticReconnect = true;

    private long automaticReconnectMaxDelay = 5;

    private long automaticReconnectInitialDelay = 1;

    private Set<String> topics = new HashSet<>(0);

    private int nettyThreads = 32;

    private boolean retain = false;

    private boolean noLocal = false;

}

HivemqClientV5

java 复制代码
/**
 * @author laokou
 */
@Slf4j
public class HivemqClientV5 {

    /**
     * 响应主题.
     */
    private final String RESPONSE_TOPIC = "response/topic";

    /**
     * 服务下线数据.
     */
    private final byte[] WILL_PAYLOAD = "offline".getBytes(UTF_8);

    /**
     * 相关数据.
     */
    private final byte[] CORRELATION_DATA = "correlationData".getBytes(UTF_8);

    private final HivemqProperties hivemqProperties;

    private final List<MessageHandler> messageHandlers;

    private volatile Mqtt5RxClient client;

    private final Object lock = new Object();

    private volatile Disposable connectDisposable;

    private volatile Disposable subscribeDisposable;

    private volatile Disposable unSubscribeDisposable;

    private volatile Disposable publishDisposable;

    private volatile Disposable disconnectDisposable;

    private volatile Disposable consumeDisposable;

    public HivemqClientV5(HivemqProperties hivemqProperties, List<MessageHandler> messageHandlers) {
        this.hivemqProperties = hivemqProperties;
        this.messageHandlers = messageHandlers;
    }

    public void open() {
        if (Objects.isNull(client)) {
            synchronized (lock) {
                if (Objects.isNull(client)) {
                    client = getMqtt5ClientBuilder().buildRx();
                }
            }
        }
        connect();
        consume();
    }

    public void close() {
        if (!Objects.isNull(client)) {
            disconnectDisposable = client.disconnectWith()
                    .sessionExpiryInterval(hivemqProperties.getSessionExpiryInterval())
                    .applyDisconnect()
                    .subscribeOn(Schedulers.io())
                    .retryWhen(errors -> errors.scan(1, (retryCount, error) -> retryCount > 5 ? -1 : retryCount + 1)
                            .takeWhile(retryCount -> retryCount != -1)
                            .flatMap(retryCount -> Flowable.timer((long) Math.pow(2, retryCount) * 100, TimeUnit.MILLISECONDS)))
                    .subscribe(() -> log.info("【Hivemq-V5】 => MQTT断开连接成功,客户端ID:{}", hivemqProperties.getClientId()),
                            e -> log.error("【Hivemq-V5】 => MQTT断开连接失败,错误信息:{}", e.getMessage(), e));
        }
    }

    public void subscribe() {
        String[] topics = getTopics();
        subscribe(topics, getQosArray(topics));
    }

    public String[] getTopics() {
        return hivemqProperties.getTopics().toArray(String[]::new);
    }

    public int[] getQosArray(String[] topics) {
        return Stream.of(topics).mapToInt(item -> hivemqProperties.getSubscribeQos()).toArray();
    }

    public void subscribe(String[] topics, int[] qosArray) {
        checkTopicAndQos(topics, qosArray);
        if (!Objects.isNull(client)) {
            List<Mqtt5Subscription> subscriptions = new ArrayList<>(topics.length);
            for (int i = 0; i < topics.length; i++) {
                subscriptions.add(Mqtt5Subscription.builder()
                        .topicFilter(topics[i])
                        .qos(getMqttQos(qosArray[i]))
                        .retainAsPublished(hivemqProperties.isRetain())
                        .noLocal(hivemqProperties.isNoLocal())
                        .build());
            }
            subscribeDisposable = client.subscribeWith()
                    .addSubscriptions(subscriptions)
                    .applySubscribe()
                    .subscribeOn(Schedulers.io())
                    .retryWhen(errors -> errors.scan(1, (retryCount, error) -> retryCount > 5 ? -1 : retryCount + 1)
                            .takeWhile(retryCount -> retryCount != -1)
                            .flatMap(retryCount -> Flowable.timer((long) Math.pow(2, retryCount) * 100, TimeUnit.MILLISECONDS)))
                    .subscribe(ack -> log.info("【Hivemq-V5】 => MQTT订阅成功,主题: {}", String.join("、", topics)), e -> log
                            .error("【Hivemq-V5】 => MQTT订阅失败,主题:{},错误信息:{}", String.join("、", topics), e.getMessage(), e));
        }
    }

    public void unSubscribe() {
        String[] topics = hivemqProperties.getTopics().toArray(String[]::new);
        unSubscribe(topics);
    }

    public void unSubscribe(String[] topics) {
        checkTopic(topics);
        if (!Objects.isNull(client)) {
            List<MqttTopicFilter> matchedTopics = new ArrayList<>(topics.length);
            for (String topic : topics) {
                matchedTopics.add(MqttTopicFilter.of(topic));
            }
            unSubscribeDisposable = client.unsubscribeWith()
                    .addTopicFilters(matchedTopics)
                    .applyUnsubscribe()
                    .subscribeOn(Schedulers.io())
                    .retryWhen(errors -> errors.scan(1, (retryCount, error) -> retryCount > 5 ? -1 : retryCount + 1)
                            .takeWhile(retryCount -> retryCount != -1)
                            .flatMap(retryCount -> Flowable.timer((long) Math.pow(2, retryCount) * 100, TimeUnit.MILLISECONDS)))
                    .subscribe(ack -> log.info("【Hivemq-V5】 => MQTT取消订阅成功,主题:{}", String.join("、", topics)), e -> log
                            .error("【Hivemq-V5】 => MQTT取消订阅失败,主题:{},错误信息:{}", String.join("、", topics), e.getMessage(), e));
        }
    }

    public void publish(String topic, byte[] payload, int qos) {
        if (!Objects.isNull(client)) {
            publishDisposable = client
                    .publish(Flowable.just(Mqtt5Publish.builder()
                            .topic(topic)
                            .qos(getMqttQos(qos))
                            .payload(payload)
                            .noMessageExpiry()
                            .retain(hivemqProperties.isRetain())
                            .messageExpiryInterval(hivemqProperties.getMessageExpiryInterval())
                            .correlationData(CORRELATION_DATA)
                            .payloadFormatIndicator(Mqtt5PayloadFormatIndicator.UTF_8)
                            .contentType("text/plain")
                            .responseTopic(RESPONSE_TOPIC)
                            .build()))
                    .subscribeOn(Schedulers.io())
                    .retryWhen(errors -> errors.scan(1, (retryCount, error) -> retryCount > 5 ? -1 : retryCount + 1)
                            .takeWhile(retryCount -> retryCount != -1)
                            .flatMap(retryCount -> Flowable.timer((long) Math.pow(2, retryCount) * 100, TimeUnit.MILLISECONDS)))
                    .subscribe(ack -> log.info("【Hivemq-V5】 => MQTT消息发布成功,topic:{}", topic),
                            e -> log.error("【Hivemq-V5】 => MQTT消息发布失败,topic:{},错误信息:{}", topic, e.getMessage(), e));
        }
    }

    public void publish(String topic, byte[] payload) {
        publish(topic, payload, hivemqProperties.getPublishQos());
    }

    public void dispose(Disposable disposable) {
        if (!Objects.isNull(disposable) && !disposable.isDisposed()) {
            // 显式取消订阅
            disposable.dispose();
        }
    }

    public void dispose() {
        dispose(connectDisposable);
        dispose(subscribeDisposable);
        dispose(unSubscribeDisposable);
        dispose(publishDisposable);
        dispose(consumeDisposable);
        dispose(disconnectDisposable);
    }

    public void reSubscribe() {
        log.info("【Hivemq-V5】 => MQTT重新订阅开始");
        dispose(subscribeDisposable);
        subscribe();
        log.info("【Hivemq-V5】 => MQTT重新订阅结束");
    }

    private MqttQos getMqttQos(int qos) {
        return MqttQos.fromCode(qos);
    }

    private void connect() {
        connectDisposable = client.connectWith()
                .keepAlive(hivemqProperties.getKeepAliveInterval())
                .cleanStart(hivemqProperties.isClearStart())
                .sessionExpiryInterval(hivemqProperties.getSessionExpiryInterval())
                .willPublish()
                .topic("will/topic")
                .payload(WILL_PAYLOAD)
                .qos(getMqttQos(hivemqProperties.getWillQos()))
                .retain(true)
                .messageExpiryInterval(100)
                .delayInterval(10)
                .payloadFormatIndicator(Mqtt5PayloadFormatIndicator.UTF_8)
                .contentType("text/plain")
                .responseTopic(RESPONSE_TOPIC)
                .correlationData(CORRELATION_DATA)
                .applyWillPublish()
                .restrictions()
                .receiveMaximum(hivemqProperties.getReceiveMaximum())
                .sendMaximum(hivemqProperties.getSendMaximum())
                .maximumPacketSize(hivemqProperties.getMaximumPacketSize())
                .sendMaximumPacketSize(hivemqProperties.getSendMaximumPacketSize())
                .topicAliasMaximum(hivemqProperties.getTopicAliasMaximum())
                .sendTopicAliasMaximum(hivemqProperties.getSendTopicAliasMaximum())
                .requestProblemInformation(hivemqProperties.isRequestProblemInformation())
                .requestResponseInformation(hivemqProperties.isRequestResponseInformation())
                .applyRestrictions()
                .applyConnect()
                .toFlowable()
                .firstElement()
                .subscribeOn(Schedulers.io())
                .retryWhen(errors -> errors.scan(1, (retryCount, error) -> retryCount > 5 ? -1 : retryCount + 1)
                        .takeWhile(retryCount -> retryCount != -1)
                        .flatMap(retryCount -> Flowable.timer((long) Math.pow(2, retryCount) * 100, TimeUnit.MILLISECONDS)))
                .subscribe(
                        ack -> log.info("【Hivemq-V5】 => MQTT连接成功,主机:{},端口:{},客户端ID:{}", hivemqProperties.getHost(),
                                hivemqProperties.getPort(), hivemqProperties.getClientId()),
                        e -> log.error("【Hivemq-V5】 => MQTT连接失败,错误信息:{}", e.getMessage(), e));
    }

    private void consume() {
        if (!Objects.isNull(client)) {
            consumeDisposable = client.publishes(MqttGlobalPublishFilter.ALL)
                    .onBackpressureBuffer(8192)
                    .observeOn(Schedulers.computation(), false, 8192)
                    .doOnSubscribe(subscribe -> {
                        log.info("【Hivemq-V5】 => MQTT开始订阅消息,请稍候。。。。。。");
                        reSubscribe();
                    })
                    .subscribeOn(Schedulers.io())
                    .retryWhen(errors -> errors.scan(1, (retryCount, error) -> retryCount > 5 ? -1 : retryCount + 1)
                            .takeWhile(retryCount -> retryCount != -1)
                            .flatMap(retryCount -> Flowable.timer((long) Math.pow(2, retryCount) * 100, TimeUnit.MILLISECONDS)))
                    .subscribe(publish -> {
                                for (MessageHandler messageHandler : messageHandlers) {
                                    if (messageHandler.isSubscribe(publish.getTopic().toString())) {
                                        log.info("【Hivemq-V5】 => MQTT接收到消息,Topic:{}", publish.getTopic());
                                        messageHandler
                                                .handle(new MqttMessage(publish.getPayloadAsBytes(), publish.getTopic().toString()));
                                    }
                                }
                            }, e -> log.error("【Hivemq-V5】 => MQTT消息处理失败,错误信息:{}", e.getMessage(), e),
                            () -> log.info("【Hivemq-V5】 => MQTT订阅消息结束,请稍候。。。。。。"));
        }
    }

    private Mqtt5ClientBuilder getMqtt5ClientBuilder() {
        Mqtt5ClientBuilder builder = Mqtt5Client.builder().addConnectedListener(listener -> {
                    Optional<? extends MqttClientConnectionConfig> config = Optional
                            .of(listener.getClientConfig().getConnectionConfig())
                            .get();
                    config.ifPresent(mqttClientConnectionConfig -> log.info("【Hivemq-V5】 => MQTT连接保持时间:{}ms",
                            mqttClientConnectionConfig.getKeepAlive()));
                    log.info("【Hivemq-V5】 => MQTT已连接,客户端ID:{}", hivemqProperties.getClientId());
                })
                .addDisconnectedListener(
                        listener -> log.error("【Hivemq-V5】 => MQTT已断开连接,客户端ID:{}", hivemqProperties.getClientId()))
                .identifier(hivemqProperties.getClientId())
                .serverHost(hivemqProperties.getHost())
                .serverPort(hivemqProperties.getPort())
                .executorConfig(MqttClientExecutorConfig.builder()
                        .nettyExecutor(ThreadUtils.newVirtualTaskExecutor())
                        .nettyThreads(hivemqProperties.getNettyThreads())
                        .applicationScheduler(Schedulers.from(ThreadUtils.newVirtualTaskExecutor()))
                        .build());
        // 开启重连
        if (hivemqProperties.isAutomaticReconnect()) {
            builder.automaticReconnect()
                    .initialDelay(hivemqProperties.getAutomaticReconnectInitialDelay(), TimeUnit.SECONDS)
                    .maxDelay(hivemqProperties.getAutomaticReconnectMaxDelay(), TimeUnit.SECONDS)
                    .applyAutomaticReconnect();
        }
        if (hivemqProperties.isAuth()) {
            builder.simpleAuth()
                    .username(hivemqProperties.getUsername())
                    .password(hivemqProperties.getPassword().getBytes())
                    .applySimpleAuth();
        }
        return builder;
    }

    private void checkTopicAndQos(String[] topics, int[] qosArray) {
        if (topics == null || qosArray == null) {
            throw new IllegalArgumentException("【" + "Hivemq-V5" + "】 => Topics and QoS arrays cannot be null");
        }
        if (topics.length != qosArray.length) {
            throw new IllegalArgumentException("【" + "Hivemq-V5" + "】 => Topics and QoS arrays must have the same length");
        }
        if (topics.length == 0) {
            throw new IllegalArgumentException("【" + "Hivemq-V5" + "】 => Topics array cannot be empty");
        }
    }

    private void checkTopic(String[] topics) {
        if (topics.length == 0) {
            throw new IllegalArgumentException("【" + "Hivemq-V5" + "】 => Topics array cannot be empty");
        }
    }

}

HivemqV5MqttClientTest

java 复制代码
/**
 * @author laokou
 */
@SpringBootTest
@RequiredArgsConstructor
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
class HivemqV5MqttClientTest {

    private final List<MessageHandler> messageHandlers;

    @Test
    void testMqttClient() throws InterruptedException {
        HivemqProperties hivemqProperties = new HivemqProperties();
        hivemqProperties.setClientId("test-client-1");
        hivemqProperties.setTopics(Set.of("/test-topic-1/#"));
        HivemqClientV5 hivemqClientV5 = new HivemqClientV5(hivemqProperties, messageHandlers);
        hivemqClientV5.open();
        hivemqClientV5.publish("/test-topic-1/123", "Hello World123".getBytes());
    }

}

HivemqClientV3

java 复制代码
/**
 * @author laokou
 */
@Slf4j
public class HivemqClientV3 {

    /**
     * 服务下线数据.
     */
    private final byte[] WILL_PAYLOAD = "offline".getBytes(UTF_8);

    private final HivemqProperties hivemqProperties;

    private final List<MessageHandler> messageHandlers;

    private volatile Mqtt3RxClient client;

    private final Object lock = new Object();

    private volatile Disposable connectDisposable;

    private volatile Disposable subscribeDisposable;

    private volatile Disposable unSubscribeDisposable;

    private volatile Disposable publishDisposable;

    private volatile Disposable disconnectDisposable;

    private volatile Disposable consumeDisposable;

    public HivemqClientV3(HivemqProperties hivemqProperties, List<MessageHandler> messageHandlers) {
        this.hivemqProperties = hivemqProperties;
        this.messageHandlers = messageHandlers;
    }

    public void open() {
        if (Objects.isNull(client)) {
            synchronized (lock) {
                if (Objects.isNull(client)) {
                    client = getMqtt3ClientBuilder().buildRx();
                }
            }
        }
        connect();
        consume();
    }

    public void close() {
        if (!Objects.isNull(client)) {
            disconnectDisposable = client.disconnect()
                    .subscribeOn(Schedulers.io())
                    .retryWhen(errors -> errors.scan(1, (retryCount, error) -> retryCount > 5 ? -1 : retryCount + 1)
                            .takeWhile(retryCount -> retryCount != -1)
                            .flatMap(retryCount -> Flowable.timer((long) Math.pow(2, retryCount) * 100, TimeUnit.MILLISECONDS)))
                    .subscribe(() -> log.info("【Hivemq-V3】 => MQTT断开连接成功,客户端ID:{}", hivemqProperties.getClientId()),
                            e -> log.error("【Hivemq-V3】 => MQTT断开连接失败,错误信息:{}", e.getMessage(), e));
        }
    }

    public void subscribe() {
        String[] topics = getTopics();
        subscribe(topics, getQosArray(topics));
    }

    public String[] getTopics() {
        return hivemqProperties.getTopics().toArray(String[]::new);
    }

    public int[] getQosArray(String[] topics) {
        return Stream.of(topics).mapToInt(item -> hivemqProperties.getSubscribeQos()).toArray();
    }

    public void subscribe(String[] topics, int[] qosArray) {
        checkTopicAndQos(topics, qosArray);
        if (!Objects.isNull(client)) {
            List<Mqtt3Subscription> subscriptions = new ArrayList<>(topics.length);
            for (int i = 0; i < topics.length; i++) {
                subscriptions.add(Mqtt3Subscription.builder()
                        .topicFilter(topics[i])
                        .qos(getMqttQos(qosArray[i]))
                        .build());
            }
            subscribeDisposable = client.subscribeWith()
                    .addSubscriptions(subscriptions)
                    .applySubscribe()
                    .subscribeOn(Schedulers.io())
                    .retryWhen(errors -> errors.scan(1, (retryCount, error) -> retryCount > 5 ? -1 : retryCount + 1)
                            .takeWhile(retryCount -> retryCount != -1)
                            .flatMap(retryCount -> Flowable.timer((long) Math.pow(2, retryCount) * 100, TimeUnit.MILLISECONDS)))
                    .subscribe(ack -> log.info("【Hivemq-V3】 => MQTT订阅成功,主题: {}", String.join("、", topics)), e -> log
                            .error("【Hivemq-V3】 => MQTT订阅失败,主题:{},错误信息:{}", String.join("、", topics), e.getMessage(), e));
        }
    }

    public void unSubscribe() {
        String[] topics = hivemqProperties.getTopics().toArray(String[]::new);
        unSubscribe(topics);
    }

    public void unSubscribe(String[] topics) {
        checkTopic(topics);
        if (!Objects.isNull(client)) {
            List<MqttTopicFilter> matchedTopics = new ArrayList<>(topics.length);
            for (String topic : topics) {
                matchedTopics.add(MqttTopicFilter.of(topic));
            }
            unSubscribeDisposable = client.unsubscribeWith()
                    .addTopicFilters(matchedTopics)
                    .applyUnsubscribe()
                    .subscribeOn(Schedulers.io())
                    .retryWhen(errors -> errors.scan(1, (retryCount, error) -> retryCount > 5 ? -1 : retryCount + 1)
                            .takeWhile(retryCount -> retryCount != -1)
                            .flatMap(retryCount -> Flowable.timer((long) Math.pow(2, retryCount) * 100, TimeUnit.MILLISECONDS)))
                    .subscribe(() -> log.info("【Hivemq-V3】 => MQTT取消订阅成功,主题:{}", String.join("、", topics)), e -> log
                            .error("【Hivemq-V3】 => MQTT取消订阅失败,主题:{},错误信息:{}", String.join("、", topics), e.getMessage(), e));
        }
    }

    public void publish(String topic, byte[] payload, int qos) {
        if (!Objects.isNull(client)) {
            publishDisposable = client
                    .publish(Flowable.just(Mqtt3Publish.builder()
                            .topic(topic)
                            .qos(getMqttQos(qos))
                            .payload(payload)
                            .retain(hivemqProperties.isRetain())
                            .build()))
                    .subscribeOn(Schedulers.io())
                    .retryWhen(errors -> errors.scan(1, (retryCount, error) -> retryCount > 5 ? -1 : retryCount + 1)
                            .takeWhile(retryCount -> retryCount != -1)
                            .flatMap(retryCount -> Flowable.timer((long) Math.pow(2, retryCount) * 100, TimeUnit.MILLISECONDS)))
                    .subscribe(ack -> log.info("【Hivemq-V3】 => MQTT消息发布成功,topic:{}", topic),
                            e -> log.error("【Hivemq-V3】 => MQTT消息发布失败,topic:{},错误信息:{}", topic, e.getMessage(), e));
        }
    }

    public void publish(String topic, byte[] payload) {
        publish(topic, payload, hivemqProperties.getPublishQos());
    }

    public void dispose(Disposable disposable) {
        if (!Objects.isNull(disposable) && !disposable.isDisposed()) {
            // 显式取消订阅
            disposable.dispose();
        }
    }

    public void dispose() {
        dispose(connectDisposable);
        dispose(subscribeDisposable);
        dispose(unSubscribeDisposable);
        dispose(publishDisposable);
        dispose(consumeDisposable);
        dispose(disconnectDisposable);
    }

    public void reSubscribe() {
        log.info("【Hivemq-V3】 => MQTT重新订阅开始");
        dispose(subscribeDisposable);
        subscribe();
        log.info("【Hivemq-V3】 => MQTT重新订阅结束");
    }

    private MqttQos getMqttQos(int qos) {
        return MqttQos.fromCode(qos);
    }

    private void connect() {
        connectDisposable = client.connectWith()
                .keepAlive(hivemqProperties.getKeepAliveInterval())
                .willPublish()
                .topic("will/topic")
                .payload(WILL_PAYLOAD)
                .qos(getMqttQos(hivemqProperties.getWillQos()))
                .retain(true)
                .applyWillPublish()
                .restrictions()
                .sendMaximum(hivemqProperties.getSendMaximum())
                .sendMaximumPacketSize(hivemqProperties.getSendMaximumPacketSize())
                .applyRestrictions()
                .applyConnect()
                .toFlowable()
                .firstElement()
                .subscribeOn(Schedulers.io())
                .retryWhen(errors -> errors.scan(1, (retryCount, error) -> retryCount > 5 ? -1 : retryCount + 1)
                        .takeWhile(retryCount -> retryCount != -1)
                        .flatMap(retryCount -> Flowable.timer((long) Math.pow(2, retryCount) * 100, TimeUnit.MILLISECONDS)))
                .subscribe(
                        ack -> log.info("【Hivemq-V3】 => MQTT连接成功,主机:{},端口:{},客户端ID:{}", hivemqProperties.getHost(),
                                hivemqProperties.getPort(), hivemqProperties.getClientId()),
                        e -> log.error("【Hivemq-V3】 => MQTT连接失败,错误信息:{}", e.getMessage(), e));
    }

    private void consume() {
        if (!Objects.isNull(client)) {
            consumeDisposable = client.publishes(MqttGlobalPublishFilter.ALL)
                    .onBackpressureBuffer(8192)
                    .observeOn(Schedulers.computation(), false, 8192)
                    .doOnSubscribe(subscribe -> {
                        log.info("【Hivemq-V3】 => MQTT开始订阅消息,请稍候。。。。。。");
                        reSubscribe();
                    })
                    .subscribeOn(Schedulers.io())
                    .retryWhen(errors -> errors.scan(1, (retryCount, error) -> retryCount > 5 ? -1 : retryCount + 1)
                            .takeWhile(retryCount -> retryCount != -1)
                            .flatMap(retryCount -> Flowable.timer((long) Math.pow(2, retryCount) * 100, TimeUnit.MILLISECONDS)))
                    .subscribe(publish -> {
                                for (MessageHandler messageHandler : messageHandlers) {
                                    if (messageHandler.isSubscribe(publish.getTopic().toString())) {
                                        log.info("【Hivemq-V3】 => MQTT接收到消息,Topic:{}", publish.getTopic());
                                        messageHandler
                                                .handle(new MqttMessage(publish.getPayloadAsBytes(), publish.getTopic().toString()));
                                    }
                                }
                            }, e -> log.error("【Hivemq-V3】 => MQTT消息处理失败,错误信息:{}", e.getMessage(), e),
                            () -> log.info("【Hivemq-V3】 => MQTT订阅消息结束,请稍候。。。。。。"));
        }
    }

    private Mqtt3ClientBuilder getMqtt3ClientBuilder() {
        Mqtt3ClientBuilder builder = Mqtt3Client.builder().addConnectedListener(listener -> {
                    Optional<? extends MqttClientConnectionConfig> config = Optional
                            .of(listener.getClientConfig().getConnectionConfig())
                            .get();
                    config.ifPresent(mqttClientConnectionConfig -> log.info("【Hivemq-V5】 => MQTT连接保持时间:{}ms",
                            mqttClientConnectionConfig.getKeepAlive()));
                    log.info("【Hivemq-V3】 => MQTT已连接,客户端ID:{}", hivemqProperties.getClientId());
                })
                .addDisconnectedListener(
                        listener -> log.error("【Hivemq-V3】 => MQTT已断开连接,客户端ID:{}", hivemqProperties.getClientId()))
                .identifier(hivemqProperties.getClientId())
                .serverHost(hivemqProperties.getHost())
                .serverPort(hivemqProperties.getPort())
                .executorConfig(MqttClientExecutorConfig.builder()
                        .nettyExecutor(ThreadUtils.newVirtualTaskExecutor())
                        .nettyThreads(hivemqProperties.getNettyThreads())
                        .applicationScheduler(Schedulers.from(ThreadUtils.newVirtualTaskExecutor()))
                        .build());
        // 开启重连
        if (hivemqProperties.isAutomaticReconnect()) {
            builder.automaticReconnect()
                    .initialDelay(hivemqProperties.getAutomaticReconnectInitialDelay(), TimeUnit.SECONDS)
                    .maxDelay(hivemqProperties.getAutomaticReconnectMaxDelay(), TimeUnit.SECONDS)
                    .applyAutomaticReconnect();
        }
        if (hivemqProperties.isAuth()) {
            builder.simpleAuth()
                    .username(hivemqProperties.getUsername())
                    .password(hivemqProperties.getPassword().getBytes())
                    .applySimpleAuth();
        }
        return builder;
    }

    private void checkTopicAndQos(String[] topics, int[] qosArray) {
        if (topics == null || qosArray == null) {
            throw new IllegalArgumentException("【" + "Hivemq-V3" + "】 => Topics and QoS arrays cannot be null");
        }
        if (topics.length != qosArray.length) {
            throw new IllegalArgumentException("【" + "Hivemq-V3" + "】 => Topics and QoS arrays must have the same length");
        }
        if (topics.length == 0) {
            throw new IllegalArgumentException("【" + "Hivemq-V3" + "】 => Topics array cannot be empty");
        }
    }

    private void checkTopic(String[] topics) {
        if (topics.length == 0) {
            throw new IllegalArgumentException("【" + "Hivemq-V3" + "】 => Topics array cannot be empty");
        }
    }

}

HivemqV3MqttClientTest

java 复制代码
/**
 * @author laokou
 */
@SpringBootTest
@RequiredArgsConstructor
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
class HivemqV3MqttClientTest {

    private final List<MessageHandler> messageHandlers;

    @Test
    void testMqttClient() throws InterruptedException {
        HivemqProperties hivemqProperties2 = new HivemqProperties();
        hivemqProperties2.setClientId("test-client-2");
        hivemqProperties2.setTopics(Set.of("/test-topic-2/#"));
        HivemqClientV3 hivemqClientV3 = new HivemqClientV3(hivemqProperties2, messageHandlers);
        hivemqClientV3.open();
        hivemqClientV3.publish("/test-topic-2/456", "Hello World456".getBytes());
    }

}

Vert.x MQTT Client【推荐,只兼容mqtt3.1.1】

# Vert.x MQTT文档

引入依赖
xml 复制代码
<dependencies>
    <dependency>
      <groupId>io.vertx</groupId>
      <artifactId>vertx-mqtt</artifactId>
      <version>4.5.14</version>
    </dependency>
    <dependency>
      <groupId>io.projectreactor</groupId>
      <artifactId>reactor-core</artifactId>
      <version>3.7.5</version>
    </dependency>
</dependencies>
项目集成

MqttClientProperties

java 复制代码
/**
 * @author laokou
 */
@Data
public class MqttClientProperties {

    private boolean auth = true;

    private String username = "emqx";

    private String password = "laokou123";

    private String host = "127.0.0.1";

    private int port = 1883;

    private String clientId = UUIDGenerator.generateUUID();

    // @formatter:off
    /**
     * 控制是否创建新会话(true=新建,false=复用历史会话). clearStart=true => Broker 会在连接断开后立即清除所有会话信息.
     * clearStart=false => Broker 会在连接断开后保存会话信息,并在重新连接后复用会话信息.
     */
    // @formatter:on
    private boolean clearSession = false;

    private int receiveBufferSize = Integer.MAX_VALUE;

    private int maxMessageSize = -1;

    /**
     * 心跳包每隔60秒发一次.
     */
    private int keepAliveInterval = 60;

    private boolean autoKeepAlive = true;

    private long reconnectInterval = 1000;

    private int reconnectAttempts = Integer.MAX_VALUE;

    private Map<String, Integer> topics = new HashMap<>(0);

    private int willQos = 1;

    private boolean willRetain = false;

    private int ackTimeout = -1;

    private boolean autoAck = true;

    /**
     * 服务下线主题.
     */
    private String willTopic = "/will";

    /**
     * 服务下线数据.
     */
    private String willPayload = "offline";

}

VertxConfig

java 复制代码
/**
 * @author laokou
 */
@Configuration
public class VertxConfig {

    @Bean
    public Vertx vertx() {
       VertxOptions vertxOptions = new VertxOptions();
       vertxOptions.setMaxEventLoopExecuteTime(60);
       vertxOptions.setMaxWorkerExecuteTime(60);
       vertxOptions.setMaxEventLoopExecuteTimeUnit(TimeUnit.SECONDS);
       vertxOptions.setMaxWorkerExecuteTimeUnit(TimeUnit.SECONDS);
       vertxOptions.setPreferNativeTransport(true);
       return Vertx.vertx(vertxOptions);
    }

}

VertxMqttClient

注意:vertx-mqtt不支持客户端自动断线重连,网络不通畅或连接关闭,需要自己手动调用连接!!!实现这个重连的功能

java 复制代码
/**
 * @author laokou
 */
@Slf4j
public class VertxMqttClient {

    private final Sinks.Many<MqttPublishMessage> messageSink = Sinks.many()
       .multicast()
       .onBackpressureBuffer(Integer.MAX_VALUE, false);

    private final MqttClient mqttClient;

    private final Vertx vertx;

    private final MqttClientProperties mqttClientProperties;

    private final List<MessageHandler> messageHandlers;

    private final List<Disposable> disposables;

    private final AtomicBoolean isConnected = new AtomicBoolean(false);

    private final AtomicBoolean isLoaded = new AtomicBoolean(false);

    private final AtomicBoolean isReconnected = new AtomicBoolean(true);

    public VertxMqttClient(final Vertx vertx, final MqttClientProperties mqttClientProperties,
          final List<MessageHandler> messageHandlers) {
       this.vertx = vertx;
       this.mqttClientProperties = mqttClientProperties;
       this.mqttClient = MqttClient.create(vertx, getOptions());
       this.messageHandlers = messageHandlers;
       this.disposables = Collections.synchronizedList(new ArrayList<>());
    }

    public void open() {
       mqttClient.closeHandler(v -> {
          isConnected.set(false);
          log.error("【Vertx-MQTT】 => MQTT连接断开,客户端ID:{}", mqttClientProperties.getClientId());
          reconnect();
       })
          .publishHandler(messageSink::tryEmitNext)
          // 仅接收QoS1和QoS2的消息
          .publishCompletionHandler(id -> {
             // log.info("【Vertx-MQTT】 => 接收MQTT的PUBACK或PUBCOMP数据包,数据包ID:{}", id);
          })
          .subscribeCompletionHandler(ack -> {
             // log.info("【Vertx-MQTT】 => 接收MQTT的SUBACK数据包,数据包ID:{}", ack.messageId());
          })
          .unsubscribeCompletionHandler(id -> {
             // log.info("【Vertx-MQTT】 => 接收MQTT的UNSUBACK数据包,数据包ID:{}", id);
          })
          .pingResponseHandler(s -> {
             // log.info("【Vertx-MQTT】 => 接收MQTT的PINGRESP数据包");
          })
          .connect(mqttClientProperties.getPort(), mqttClientProperties.getHost(), connectResult -> {
             if (connectResult.succeeded()) {
                isConnected.set(true);
                log.info("【Vertx-MQTT】 => MQTT连接成功,主机:{},端口:{},客户端ID:{}", mqttClientProperties.getHost(),
                      mqttClientProperties.getPort(), mqttClientProperties.getClientId());
                resubscribe();
             }
             else {
                isConnected.set(false);
                Throwable ex = connectResult.cause();
                log.error("【Vertx-MQTT】 => MQTT连接失败,原因:{},客户端ID:{}", ex.getMessage(),
                      mqttClientProperties.getClientId(), ex);
                reconnect();
             }
          });
    }

    public void close() {
       disconnect();
    }

    /**
     * Sends the PUBLISH message to the remote MQTT server.
     * @param topic topic on which the message is published
     * @param payload message payload
     * @param qos QoS level
     * @param isDup if the message is a duplicate
     * @param isRetain if the message needs to be retained
     */
    public void publish(String topic, int qos, String payload, boolean isDup, boolean isRetain) {
       mqttClient.publish(topic, Buffer.buffer(payload), convertQos(qos), isDup, isRetain);
    }

    private void reconnect() {
       if (isReconnected.get()) {
          log.info("【Vertx-MQTT】 => MQTT尝试重连");
          vertx.setTimer(mqttClientProperties.getReconnectInterval(),
                handler -> ThreadUtils.newVirtualTaskExecutor().execute(this::open));
       }
    }

    private void subscribe() {
       Map<String, Integer> topics = mqttClientProperties.getTopics();
       checkTopicAndQos(topics);
       mqttClient.subscribe(topics, subscribeResult -> {
          if (subscribeResult.succeeded()) {
             log.info("【Vertx-MQTT】 => MQTT订阅成功,主题: {}", String.join("、", topics.keySet()));
          }
          else {
             Throwable ex = subscribeResult.cause();
             log.error("【Vertx-MQTT】 => MQTT订阅失败,主题:{},错误信息:{}", String.join("、", topics.keySet()), ex.getMessage(),
                   ex);
          }
       });
    }

    private void resubscribe() {
       if (isConnected.get() || mqttClient.isConnected()) {
          ThreadUtils.newVirtualTaskExecutor().execute(this::subscribe);
       }
       if (isLoaded.compareAndSet(false, true)) {
          ThreadUtils.newVirtualTaskExecutor().execute(this::consume);
       }
    }

    private void consume() {
       Disposable disposable = messageSink.asFlux().doOnNext(mqttPublishMessage -> {
          String topic = mqttPublishMessage.topicName();
          log.info("【Vertx-MQTT】 => MQTT接收到消息,Topic:{}", topic);
          for (MessageHandler messageHandler : messageHandlers) {
             if (messageHandler.isSubscribe(topic)) {
                messageHandler.handle(new MqttMessage(mqttPublishMessage.payload(), topic));
             }
          }
       }).subscribeOn(Schedulers.boundedElastic()).subscribe();
       disposables.add(disposable);
    }

    private void disposable() {
       for (Disposable disposable : disposables) {
          if (ObjectUtils.isNotNull(disposable) && !disposable.isDisposed()) {
             disposable.dispose();
          }
       }
    }

    private void disconnect() {
       isReconnected.set(false);
       mqttClient.disconnect(disconnectResult -> {
          if (disconnectResult.succeeded()) {
             disposable();
             log.info("【Vertx-MQTT】 => MQTT断开连接成功");
             disposables.clear();
          }
          else {
             Throwable ex = disconnectResult.cause();
             log.error("【Vertx-MQTT】 => MQTT断开连接失败,错误信息:{}", ex.getMessage(), ex);
          }
       });
    }

    private void unsubscribe(List<String> topics) {
       checkTopic(topics);
       mqttClient.unsubscribe(topics, unsubscribeResult -> {
          if (unsubscribeResult.succeeded()) {
             log.info("【Vertx-MQTT】 => MQTT取消订阅成功,主题:{}", String.join("、", topics));
          }
          else {
             Throwable ex = unsubscribeResult.cause();
             log.error("【Vertx-MQTT】 => MQTT取消订阅失败,主题:{},错误信息:{}", String.join("、", topics), ex.getMessage(), ex);
          }
       });
    }

    private MqttClientOptions getOptions() {
       MqttClientOptions options = new MqttClientOptions();
       options.setClientId(mqttClientProperties.getClientId());
       options.setCleanSession(mqttClientProperties.isClearSession());
       options.setAutoKeepAlive(mqttClientProperties.isAutoKeepAlive());
       options.setKeepAliveInterval(mqttClientProperties.getKeepAliveInterval());
       options.setReconnectAttempts(mqttClientProperties.getReconnectAttempts());
       options.setReconnectInterval(mqttClientProperties.getReconnectInterval());
       options.setWillQoS(mqttClientProperties.getWillQos());
       options.setWillTopic(mqttClientProperties.getWillTopic());
       options.setAutoAck(mqttClientProperties.isAutoAck());
       options.setAckTimeout(mqttClientProperties.getAckTimeout());
       options.setWillRetain(mqttClientProperties.isWillRetain());
       options.setWillMessageBytes(Buffer.buffer(mqttClientProperties.getWillPayload()));
       options.setReceiveBufferSize(mqttClientProperties.getReceiveBufferSize());
       options.setMaxMessageSize(mqttClientProperties.getMaxMessageSize());
       if (mqttClientProperties.isAuth()) {
          options.setPassword(mqttClientProperties.getPassword());
          options.setUsername(mqttClientProperties.getUsername());
       }
       return options;
    }

    private void checkTopicAndQos(Map<String, Integer> topics) {
       topics.forEach((topic, qos) -> {
          if (StringUtils.isEmpty(topic) || ObjectUtils.isNull(qos)) {
             throw new IllegalArgumentException("【Vertx-MQTT】 => Topic and QoS cannot be null");
          }
       });
    }

    private void checkTopic(List<String> topics) {
       if (CollectionUtils.isEmpty(topics)) {
          throw new IllegalArgumentException("【Vertx-MQTT】 => Topics list cannot be empty");
       }
    }

    private MqttQoS convertQos(int qos) {
       return MqttQoS.valueOf(qos);
    }

}

VertxMqttClientTest

java 复制代码
/**
 * @author laokou
 */
@SpringBootTest
@RequiredArgsConstructor
@ContextConfiguration(classes = { DefaultMessageHandler.class, VertxConfig.class })
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
class VertxMqttClientTest {

    private final List<MessageHandler> messageHandlers;

    private final Vertx vertx;

    @Test
    void testMqttClient() throws InterruptedException {
       MqttClientProperties properties = new MqttClientProperties();
       properties.setHost("127.0.0.1");
       properties.setPort(1883);
       properties.setUsername("emqx");
       properties.setPassword("laokou123");
       properties.setClientId("test-client-1");
       properties.setTopics(Map.of("/test-topic-1/#", 1));
       VertxMqttClient vertxMqttClient = new VertxMqttClient(vertx, properties, messageHandlers);
       Assertions.assertDoesNotThrow(vertxMqttClient::open);
       Thread.sleep(500);
       Assertions.assertDoesNotThrow(() -> vertxMqttClient.publish("/test-topic-1/test", 1, "test", false, false));
       Thread.sleep(500);
       Assertions.assertDoesNotThrow(vertxMqttClient::close);
       Thread.sleep(500);
    }

}

详细代码请点击

非常推荐使用vertx-mqtt,项目平稳运行好用!!!

但是,需要时注意的是,项目部署到Linux系统,需要最少分配 -Xmx2100m -Xms2100m 内存,不然连接会关闭!

我是老寇,我们下次再见啦~

相关推荐
时光の尘15 小时前
FreeRTOS菜鸟入门(十)·消息队列
c语言·stm32·单片机·嵌入式硬件·mcu·物联网·嵌入式实时数据库
淘源码d4 天前
智慧工地:整合物联网、大数据、人工智能、BIM等技术,实现施工现场的全要素智能化管理
物联网·saas·智慧工地·数字化转型·云平台·工地管理·房建工程
天波信息技术分享4 天前
AI 边缘计算网关十大品牌
人工智能·物联网·边缘计算
无脑学c++4 天前
STM32printf重定向到串口含armcc和gcc两种方案
stm32·单片机·物联网
梁下轻语的秋缘4 天前
华为云loT物联网介绍与使用
物联网·学习·华为·华为云
龙大大L4 天前
5.4 ESP32物联网开发实战教学:三大项目详解
嵌入式硬件·物联网·esp32
by————组态4 天前
基于web组态优化策略研究
大数据·前端·物联网·低代码·数学建模·自动化
小叮当⇔5 天前
IOT项目——DIY 气象站
物联网