SpringBoot整合MQTT服务器实现消息的发送与订阅(推送消息与接收推送)

场景

Windows上Mqtt服务器搭建与使用客户端工具MqttBox进行测试:

https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/112305328

在上面搭建好了MQTT服务器以及客户端工具MqttBox之后,怎样在SpringBoot中实现订阅主题接收消息和发布主题推送消息的功能。

注:

博客:
https://blog.csdn.net/badao_liumang_qizhi
关注公众号
霸道的程序猿
获取编程相关电子书、教程推送与免费下载。

实现

首先搭建起一个SpringBoot项目,引入最基本的Web依赖,这里可以使用快速搭建框架进行搭建

若依前后端分离版手把手教你本地搭建环境并运行项目:

https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/108465662

然后搭建好SpringBoot项目后,首先在项目中引入mqtt的相关依赖

        <!--mqtt-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-integration</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.integration</groupId>
            <artifactId>spring-integration-stream</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.integration</groupId>
            <artifactId>spring-integration-mqtt</artifactId>
        </dependency>

然后连接MQTT服务器时需要配置一些参数,比如服务器地址、用户名、密码等。所以需要将这些配置放在配置文件中

 

然后在Spring节点下添加如下配置

# Spring配置
spring:
  # MQTT
  mqtt:
    # 服务器连接地址,如果有多个,用逗号隔开
    host: tcp://你自己的MQTT服务器的地址:1883
    # 连接服务器默认客户端ID
    clientId: mqtt_client_id_001
    # 默认的消息推送主题,实际可在调用接口时指定
    topic: mqtt_topic_001,mqtt_topic_002,mqtt_topic_003
    # 用户名
    username: admin
    # 密码
    password: admin
    # 连接超时
    timeout: 30
    # 心跳
    keepalive: 30

添加位置

 

这里的服务器连接地址就是上面博客搭建的MQTT服务器的地址,其端口号是mqtt协议连接的端口号,默认是1883,不是mqtt服务端后台登录的端口号。

默认的客户端id用来作为在MQTT服务端的唯一标识,然后下面的默认消息推送的主题会在项目启动后先发布这些主题,实际使用时需要在接口调用时指定。

然后用户名密码就是上面MQTT中的用户名和密码

在配置文件中添加了配置项之后需要在代码中获取到这些配置项,根据自己的项目的规范去决定是通过注解还是其他方式来获取配置项

这里使用如下方式

创建一个PropertiesUtil类用来加载配置项的的内容

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
 * 获取配置信息
 **/
public class PropertiesUtil {

    public static String MQTT_HOST;
    public static String MQTT_CLIENT_ID;
    public static String MQTT_USER_NAME;
    public static String MQTT_PASSWORD;
    public static String MQTT_TOPIC;
    public static Integer MQTT_TIMEOUT;
    public static Integer MQTT_KEEP_ALIVE;

    /**
     *  mqtt配置
     */
    static {
        Properties properties = loadMqttProperties();
        MQTT_HOST = properties.getProperty("host");
        MQTT_CLIENT_ID = properties.getProperty("clientId");
        MQTT_USER_NAME = properties.getProperty("username");
        MQTT_PASSWORD = properties.getProperty("password");
        MQTT_TOPIC = properties.getProperty("topic");
        MQTT_TIMEOUT = Integer.valueOf(properties.getProperty("timeout"));
        MQTT_KEEP_ALIVE = Integer.valueOf(properties.getProperty("keepalive"));
    }

    private static Properties loadMqttProperties() {
        InputStream inputstream = PropertiesUtil.class.getResourceAsStream("/application.yml");
        Properties properties = new Properties();
        try {
            properties.load(inputstream);
            return properties;
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                if (inputstream != null) {
                    inputstream.close();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

然后就是需要在SpringBoot项目启动后连接到mqtt服务器并创建客户端,然后在具体的业务需求中比如在Controller中进行订阅和发布主题。

所以首先创建一个MqttConsumer类,并使其实现ApplicationRunne接口的run方法以实现在项目启动后执行所需要的操作

import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;


@Component
public class MqttConsumer implements ApplicationRunner {

    private static MqttClient client;

    @Override
    public void run(ApplicationArguments args) {
        System.out.println("初始化并启动mqtt......");
        this.connect();
    }

    /**
     * 连接mqtt服务器
     */
    private void connect() {
        try {
            // 1 创建客户端
            getClient();
            // 2 设置配置
            MqttConnectOptions options = getOptions();
            String[] topic = PropertiesUtil.MQTT_TOPIC.split(",");
            // 3 消息发布质量
            int[] qos = getQos(topic.length);
            // 4 最后设置
            create(options, topic, qos);
        } catch (Exception e) {
            System.out.println("mqtt连接异常:" + e);
        }
    }

    /**
     *  创建客户端  --- 1 ---
     */
    public void getClient() {
        try {
            if (null == client) {
                client = new MqttClient(PropertiesUtil.MQTT_HOST, PropertiesUtil.MQTT_CLIENT_ID, new MemoryPersistence());
            }

            System.out.println("创建mqtt客户端:" );
        } catch (Exception e) {
            System.out.println("创建mqtt客户端异常:\" + e:" );
        }
    }

    /**
     *  生成配置对象,用户名,密码等  --- 2 ---
     */
    public MqttConnectOptions getOptions() {
        MqttConnectOptions options = new MqttConnectOptions();
        options.setUserName(PropertiesUtil.MQTT_USER_NAME);
        options.setPassword(PropertiesUtil.MQTT_PASSWORD.toCharArray());
        // 设置超时时间
        options.setConnectionTimeout(PropertiesUtil.MQTT_TIMEOUT);
        // 设置会话心跳时间
        options.setKeepAliveInterval(PropertiesUtil.MQTT_KEEP_ALIVE);
        // 是否清除session
        options.setCleanSession(false);
        System.out.println("--生成mqtt配置对象");
        return options;
    }

    /**
     *  qos   --- 3 ---
     */
    public int[] getQos(int length) {

        int[] qos = new int[length];
        for (int i = 0; i < length; i++) {
            /**
             *  MQTT协议中有三种消息发布服务质量:
             *
             * QOS0: “至多一次”,消息发布完全依赖底层 TCP/IP 网络。会发生消息丢失或重复。这一级别可用于如下情况,环境传感器数据,丢失一次读记录无所谓,因为不久后还会有第二次发送。
             * QOS1: “至少一次”,确保消息到达,但消息重复可能会发生。
             * QOS2: “只有一次”,确保消息到达一次。这一级别可用于如下情况,在计费系统中,消息重复或丢失会导致不正确的结果,资源开销大
             */
            qos[i] = 1;
        }
        System.out.println("--设置消息发布质量");
        return qos;
    }

    /**
     *  装载各种实例和订阅主题  --- 4 ---
     */
    public void create(MqttConnectOptions options, String[] topic, int[] qos) {
        try {
            client.setCallback(new MqttConsumerCallback(client, options, topic, qos));
            System.out.println("--添加回调处理类");
            client.connect(options);
        } catch (Exception e) {
            System.out.println("装载实例或订阅主题异常:" + e);
        }
    }
    /**
     * 订阅某个主题
     *
     * @param topic
     * @param qos
     */
    public static void subscribe(String topic, int qos) {
        try {
            System.out.println("topic:" + topic);
            client.subscribe(topic, qos);
        } catch (MqttException e) {
            e.printStackTrace();
        }
    }

    /**
     * 发布,非持久化
     *
     *  qos根据文档设置为1
     *
     * @param topic
     * @param msg
     */
    public static void publish(String topic, String msg) {
        publish(1, false, topic, msg);
    }

    /**
     * 发布
     */
    public static void publish(int qos, boolean retained, String topic, String pushMessage) {
        MqttMessage message = new MqttMessage();
        message.setQos(qos);
        message.setRetained(retained);
        message.setPayload(pushMessage.getBytes());
        MqttTopic mTopic = client.getTopic(topic);
        if (null == mTopic) {
            System.out.println("topic:" + topic + " 不存在");
        }
        MqttDeliveryToken token;
        try {
            token = mTopic.publish(message);
            token.waitForCompletion();

            if (!token.isComplete()) {
                System.out.println("消息发送成功");
            }
        } catch (MqttPersistenceException e) {
            e.printStackTrace();
        } catch (MqttException e) {
            e.printStackTrace();
        }
    }
}

在上面的启动类中的run方法中会首先连接mqtt服务器并创建客户端,然后加载配置文件中配置的默认主题并调用create

进行订阅。

这其中也提供了单个的用于订阅主题和发布消息的方法。

其中在订阅主题后接收消息时需要一个回调方法。

 

所以需要新建一个实现了MqttCallbackExtended接口的相关方法的回调处理类MqttConsumerCallback

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

import java.util.Arrays;

/**
 * mqtt回调处理类
 */

public class MqttConsumerCallback implements MqttCallbackExtended {

    private MqttClient client;
    private MqttConnectOptions options;
    private String[] topic;
    private int[] qos;

    public MqttConsumerCallback(MqttClient client, MqttConnectOptions options, String[] topic, int[] qos) {
        this.client = client;
        this.options = options;
        this.topic = topic;
        this.qos = qos;
    }

    /**
     * 断开重连
     */
    @Override
    public void connectionLost(Throwable cause) {
        System.out.println("MQTT连接断开,发起重连......");
        try {
            if (null != client && !client.isConnected()) {
                client.reconnect();
                System.out.println("尝试重新连接");
            } else {
                client.connect(options);
                System.out.println("尝试建立新连接");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 接收到消息调用令牌中调用
     */
    @Override
    public void deliveryComplete(IMqttDeliveryToken token) {
        System.out.println("deliveryComplete---------" + Arrays.toString(topic));
    }

    /**
     * 消息处理
     */
    @Override
    public void messageArrived(String topic, MqttMessage message) {
        try {
            String msg = new String(message.getPayload());
            System.out.println("收到topic:" + topic + " 消息:" + msg);
            System.out.println("收到消息后执行具体的业务逻辑操作,比如将消息存储进数据库");
        } catch (Exception e) {
            System.out.println("处理mqtt消息异常:" + e);
        }
    }

    /**
     * mqtt连接后订阅主题
     */
    @Override
    public void connectComplete(boolean b, String s) {
        try {
            if (null != topic && null != qos) {
                if (client.isConnected()) {
                    client.subscribe(topic, qos);
                    System.out.println("mqtt连接成功,客户端ID:" + PropertiesUtil.MQTT_CLIENT_ID);
                    System.out.println("--订阅主题::" + Arrays.toString(topic));
                } else {
                    System.out.println("mqtt连接失败,客户端ID:" + PropertiesUtil.MQTT_CLIENT_ID);
                }
            }
        } catch (Exception e) {
            System.out.println("mqtt订阅主题异常:" + e);
        }
    }
}

以上三个类建立成功之后就可以进行订阅主题和发布消息的测试了。

发布指定主题的消息

新建一个测试用的Controller接口用来测试推送消息

import com.ruoyi.web.mqtt.MqttConsumer;
import org.springframework.data.repository.query.Param;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/testmqtt")
public class MqttTest {
    /**
     * 测试推送消息
     */
    @ResponseBody
    @GetMapping(value = "/push")
    public Object push(@Param("topic") String topic,
                       @Param("msg") String msg) {
        MqttConsumer.publish(topic, msg);
        return "测试发布主题成功";
    }
}

并且把这个接口url放开权限验证可以直接在postman中进行接口调用

这里直接在接口方法中调用了上面的推送消息的方法,第一个参数是指定的主题,第二个参数是消息的内容

那么此时SpringBoot就充当了发布者的角色。

在测试推送消息前需要使用MqttBox连接到同一个MQTT服务器并订阅同一个主题,这里订阅badao主题

我们调用一下此接口

 

然后此时查看下MqttBox中已经收到消息

 

SpringBoot订阅主题并接收消息

在上面的接口中再添加一个接口用来订阅某个指定主题

    /**
     * 测试接收消息
     */
    @ResponseBody
    @GetMapping(value = "/subscribe")
    public Object subscribe(@Param("topic") String topic,
                       @Param("qus") int qus) {
        MqttConsumer.subscribe(topic, qus);
        return "订阅主题"+topic+"成功";
    }

这里订阅主题的方法第一个参数是主题,第二个是消息质量

然后再调用下此接口

 

可以看到订阅主题成功,然后我们使用MqttBox去发布一个同样主题的消息,那么SpringBoot这边的回调方法就可以接收到发送的消息并进行后续的业务操作,

比如将消息存储到数据库等。

 

  • 17
    点赞
  • 104
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

霸道流氓气质

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值