Java连接Emqx实现普通消息、保留消息、遗嘱消息的订阅与发布(附源码)

一:前提

        假设你已经安装了Emqx开源版、MQTTX客户端。若没有安装请移步官网下载安装。

Emqx:下载 EMQX 开源版。MQTTX:MQTTX:全功能 MQTT 客户端工具。安装非常简单,这里不多做说明。

二:订阅发布实现步骤

1.引入依赖

<!--MQTT客户端-->
<dependency>
    <groupId>org.eclipse.paho</groupId>
    <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
    <version>1.2.2</version>
</dependency>

<!--hutool工具类-->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.7.17</version>
</dependency>

        首先引入MQTT客户端用来连接Emqx,此外引入糊涂工具类方便我们处理JSON格式数据。

2.编辑配置文件

server:
  port: 8080
mqtt:
  broker:
    uri: tcp://localhost:1883
  client:
    id: mqtt-demo-client
  inTopic: test/topic
  outTopic: out/topic

        注意MQTT协议一般使用tcp而不是http。client.id是此Java程序的客户端id,因为当你想使用MQTT协议往某个主题发消息时,Emq服务器就会把你也当做一个客户端处理。inTopic为接收消息主题,outTopic为发布消息主题,这两个可以不用写在配置文件里。

3.读取配置文件

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;


@Configuration
@ConfigurationProperties(prefix = "mqtt")
@Data
public class MqttProperties {
    private Broker broker;
    private Client client;
    private String inTopic;
    private String outTopic;

    @Data
    public static class Broker {
        private String uri;
    }

    @Data
    public static class Client {
        private String id;
    }
}

        此类用来读取配置文件信息。

4.创建Mqtt客户端

import com.mqtt.mqttdemo.demos.properties.MqttProperties;
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.persist.MemoryPersistence;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class MqttConfig {

   @Autowired
   private MqttProperties mqttProperties;

    @Bean
    public MqttClient mqttClient() throws MqttException {
        MqttClient client = new MqttClient(mqttProperties.getBroker().getUri(), mqttProperties.getClient().getId(), new MemoryPersistence());
        MqttConnectOptions options = new MqttConnectOptions();
        // 此客户端的用户名和密码
        options.setUserName("admin");
        options.setPassword("gcl12345679".toCharArray());
        options.setCleanSession(true);
        // 设置遗嘱消息
        options.setWill(mqttProperties.getOutTopic(), "我是mqtt-demo-client,我已下线,这是我的遗嘱".getBytes(), 2, true);
        // 连接超时重试
        options.setConnectionTimeout(30);
        client.connect(options);
        return client;
    }
}

        此类用来设置Mqtt客户端的连接配置。遗嘱消息是MQTT协议支持的消息类型,简单的可以理解为当此客户端非正常下线时此消息会被发布。

5.controller层

import com.mqtt.mqttdemo.demos.service.MqttService;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;


@RestController
@Slf4j
@RequestMapping("/mqtt")
public class MqttController {

    @Autowired
    private MqttService mqttService;

    @PostMapping("/publish")
    public void publish(@RequestParam String message) {
        try {
            mqttService.publish(message);
        } catch (MqttException e) {
            log.error("发布消息失败{}", e.getMessage());
        }
        log.info("发布消息成功");
    }

    @PostMapping("/publishRetain")
    public void publishWithRetain(@RequestParam String message) {
        try {
            mqttService.publishWithRetain(message);
        } catch (MqttException e) {
            log.error("发布保留消息失败{}", e.getMessage());
        }
        log.info("发布保留消息成功");
    }

}

        保留消息也是MQTT协议支持的消息类型,设想一个场景当你往一个主题发布消息后,在发布之前已经订阅此主题的客户端可以收到此条消息,但在发布之后才订阅此主题的客户端就收不到此条消息了,但保留消息可以让发布前订阅和发布后订阅的客户端都可以收到这条消息,保留消息只允许有一条,新的保留消息会覆盖旧的。

6.service层

import cn.hutool.json.JSONUtil;
import com.influxdb.client.*;
import com.influxdb.client.domain.WritePrecision;
import com.influxdb.client.write.Point;
import com.influxdb.query.FluxTable;
import com.mqtt.mqttdemo.demos.entity.Payload;
import com.mqtt.mqttdemo.demos.properties.InfluxdbProperties;
import com.mqtt.mqttdemo.demos.properties.MqttProperties;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.servlet.http.HttpServletRequest;
import java.time.Instant;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;


@Service
@Slf4j
public class MqttService implements MqttCallback {

    @Autowired
    private MqttClient mqttClient;

    @Autowired
    private MqttProperties mqttProperties;


    

    @PostConstruct
    public void init() throws MqttException {
        mqttClient.setCallback(this);
        mqttClient.subscribe(mqttProperties.getInTopic());
        log.info("订阅主题{}", mqttProperties.getInTopic());
    }

    @PreDestroy
    public void destroy() throws MqttException {
        mqttClient.disconnect();
        log.info("与服务器断开连接");
    }

    /**
     * @description: 发送消息
     * @param: [message]
     * @return: void
     **/
    public void publish(String message) throws MqttException {
        MqttMessage mqttMessage = new MqttMessage(message.getBytes());
        // 设置消息质量为1
        mqttMessage.setQos(1);
        mqttClient.publish(mqttProperties.getOutTopic(), mqttMessage);
        log.info("向主题【{}】发布消息:【{}】", mqttProperties.getOutTopic(), message);
    }

    /**
     * @description: 发送保留消息
     * @param: [message]
     * @return: void
     **/
    public void publishWithRetain(String message) throws MqttException {
        MqttMessage mqttMessage = new MqttMessage(message.getBytes());
        // 设置消息质量为2
        mqttMessage.setQos(2);
        // 设置保留消息标志
        mqttMessage.setRetained(true);
        mqttClient.publish(mqttProperties.getOutTopic(), mqttMessage);
        log.info("向主题【{}】发布保留消息:【{}】", mqttProperties.getOutTopic(), message);
    }

    /**
     * @description: 接收消息
     * @param: [topic, message]
     * @return: void
     **/
    @Override
    public void messageArrived(String topic, MqttMessage message) throws MqttException {
        Payload payload = JSONUtil.toBean(new String(message.getPayload()), Payload.class);
        log.info("接收到来自【{}】的消息【{}】", topic, payload.getTemperature());
        if (payload.getTemperature() > 37) {
            publish("发烧");
        }
    }


    @Override
    public void connectionLost(Throwable cause) {
        log.error("连接丢失:{}", cause.getMessage());
    }


    @Override
    public void deliveryComplete(IMqttDeliveryToken token) {
        log.info("消息已送达");
    }
}

        此类实现了MqttCallback回调接口,重写了里面的三个方法,connectionLost为连接丢失时回调,deliveryComplete为消息送达确认回调,messageArrived为接收到消息时回调。

        Qos为MQTT协议的消息质量等级。具体细节请看一篇文章带你彻底搞懂MQTT协议-CSDN博客

        init方法为Bean创建时调用订阅主题。

        destory方法为Bean销毁时调用,断开连接。

7.dao层

import lombok.Data;

@Data
public class Payload {
    private Integer temperature;
}

        只有一个字段,假设发送消息格式为JSON。

{
  "temperature": 39
}

三:测试

1.启动Emqx、MQTTX、Java程序

        启动Emqx后访问http://localhost:18083/#/clients,可以看到还没有客户端上线。

        启动MQTX客户端创建一个连接并让它订阅out/topic主题。

        启动Java程序查看日志可以看到他已经订阅了test/topic主题。       

         此时Emqx管理平台可以看到两个客户端均已经上线。

2.普通消息发布订阅

        MQTTX客户端向Java客户端发送一个温度数据,大于37度时,Java客户端会回复发烧。

        可以看到普通消息发布订阅成功,并且也收到了送达确认的回调。

        至此普通消息发布订阅完成。

3.保留消息的发布订阅

        调用接口请求http://localhost:8080/mqtt/publishRetain?message=我是mqtt-demo-client的保留消息,Java客户端会发送一条保留消息。

        此时之前订阅了这个主题的客户端会受到这个保留消息。

        我们再使用MQTTX客户端创建一个连接并订阅out/topic主题。

        可以看到这个新上线的客户端也收到了这条保留消息。

        有兴趣的话可以把保留消息修改一个下重新发布,然后再新上线一个客户端,看他收到的保留消息是最新的还是之前的,即可验证保留消息只能有一条的结论。

        至此保留消息发布订阅成功。

4.遗嘱消息的发布订阅

        打开Emqx管理平台,将Java客户端踢除,模拟程序意外中断的场景。

        此时订阅了out/topic的客户端收到Java客户端的遗嘱消息。

        至此遗嘱消息发布订阅完成。

### EMQX Java 客户端断线重连解决方案 为了实现EMQXJava客户端之间的稳定连接,在遇到网络中断或其他异常情况时自动重新建立连接至关重要。`dojox.socket.Reconnect` 已经被创建来自动处理这种情况,如果套接字失去连接会尝试重新连接[^1]。 对于具体的Java MQTT客户端而言,可以利用Paho库中的内置功能来进行断线重连操作。下面是一个简单的例子展示如何配置MQTT客户端以便在网络恢复后能自动重试连接: ```java import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; import org.eclipse.paho.client.mqttv3.MqttCallbackExtended; import org.eclipse.paho.client.mqttv3.MqttClient; import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; public class MqttExample { private static final String BROKER_URL = "tcp://your_emqx_broker_address:1883"; public static void main(String[] args) throws Exception { MemoryPersistence persistence = new MemoryPersistence(); try (MqttClient client = new MqttClient(BROKER_URL, MqttClient.generateClientId(), persistence)) { MqttConnectOptions connOpts = new MqttConnectOptions(); // 设置自动重连选项 connOpts.setAutomaticReconnect(true); connOpts.setCleanSession(true); System.out.println("Connecting to broker: " + BROKER_URL); client.connect(connOpts); System.out.println("Connected"); // 注册回调函数用于监听连接状态变化和其他事件 client.setCallback(new MqttCallbackExtended() { @Override public void connectComplete(boolean reconnect, String serverURI) { if (reconnect) { System.out.println("Successfully reconnected to : " + serverURI); } } @Override public void connectionLost(Throwable cause) { System.out.println("Connection lost! Trying to reconnect..."); } @Override public void messageArrived(String topic, org.eclipse.paho.client.mqttv3.MqttMessage message) throws Exception {} @Override public void deliveryComplete(IMqttDeliveryToken token) {} }); Thread.sleep(10 * 60 * 1000); // Keep alive for testing purposes } catch (Exception e){ e.printStackTrace(); } } } ``` 在这个示例中,通过设置 `connOpts.setAutomaticReconnect(true)` 启用了自动重连特性,并且实现了 `MqttCallbackExtended` 接口的方法以监控连接的状态并响应相应的事件通知。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值