MQTT获取离线消息小议

概述

微消息队列MQ for IoT在处理离线消息时,为了简化离线消息获取机制,微消息队列系统在客户端成功建立连接并通过权限校验后,会自动加载离线消息并下发到客户端,但是实际在使用过程中会出现消费端启动后迟迟无法获取离线消息的问题,本文主要介绍延迟消息的发送与接收环节需要注意的问题。

协议相关

注意在使用SDK进行离线消息的发送过程中需要特别注意QoS和cleanSession两个参数。

  • QoS 指代消息传输的服务质量(主要针对发送端)
取值123
意义最多分发一次最多分发一次仅分发一次
  • cleanSession 建立 TCP 连接后是否关心之前状态(主要针对接收端)

true | false |
------- | ------- |
客户端再次上线时,将不再关心之前所有的订阅关系以及离线消息 | 客户端再次上线时,还需要处理之前的离线消息,而之前的订阅关系也会持续生效 |

为了处理的方便,对于处理离线消息的情况,建议不论是发送端还是接收端,参数都设置为:

QoS = 1

cleanSession = false

Java示例代码

Send Code
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import java.io.IOException;
import java.util.Date;

import static org.eclipse.paho.client.mqttv3.MqttConnectOptions.MQTT_VERSION_3_1_1;

public class MQTTSendMsg1 {

    public static void main(String[] args) throws IOException {

        final String broker ="tcp://******.mqtt.aliyuncs.com:1883";
        final String acessKey ="******";
        final String secretKey ="******";
        final String topic ="******";
        final String clientId ="GID_******@@@ClientID_device1";
        String sign;
        MemoryPersistence persistence = new MemoryPersistence();
        try {
            final MqttClient sampleClient = new MqttClient(broker, clientId, persistence);
            final MqttConnectOptions connOpts = new MqttConnectOptions();
            System.out.println("Connecting to broker: " + broker);
            sign = MacSignature.macSignature(clientId.split("@@@")[0], secretKey);
            connOpts.setUserName(acessKey);
            connOpts.setServerURIs(new String[] { broker });
            connOpts.setPassword(sign.toCharArray());
            connOpts.setCleanSession(false);
            connOpts.setKeepAliveInterval(90);
            connOpts.setAutomaticReconnect(true);
            connOpts.setMqttVersion(MQTT_VERSION_3_1_1);
            sampleClient.setCallback(new MqttCallbackExtended() {
                public void connectComplete(boolean reconnect, String serverURI) {
                    System.out.println("connect success");
                    //连接成功,需要上传客户端所有的订阅关系
                }
                public void connectionLost(Throwable throwable) {
                    System.out.println("mqtt connection lost");
                }
                public void messageArrived(String topic, MqttMessage mqttMessage) throws Exception {
                    System.out.println("messageArrived:" + topic + "------" + new String(mqttMessage.getPayload()));
                }
                public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
                    System.out.println("deliveryComplete:" + iMqttDeliveryToken.getMessageId());
                }
            });
            sampleClient.connect(connOpts);
            for (int i = 0; i < 5; i++) {
                try {
                    String scontent = new Date()+"MQTT Test body" + i;
                    //此处消息体只需要传入 byte 数组即可,对于其他类型的消息,请自行完成二进制数据的转换
                    final MqttMessage message = new MqttMessage(scontent.getBytes());
                    message.setQos(1);//设置离线消息的情况
                    System.out.println(i+" pushed at "+new Date()+" "+ scontent);
                    sampleClient.publish(topic+"/notice/", message);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        } catch (Exception me) {
            me.printStackTrace();
        }
    }
}
Receive Code
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class MQTTRecvMsg {
        public static void main(String[] args) {

            final String broker ="tcp://******.mqtt.aliyuncs.com:1883";
            final String acessKey ="******";
            final String secretKey ="******";
            final String topic ="******";
            final String clientId ="GID_******@@@ClientID_device2";
            String sign;
            MemoryPersistence persistence = new MemoryPersistence();
            try {
                final MqttClient sampleClient = new MqttClient(broker, clientId, persistence);
                final MqttConnectOptions connOpts = new MqttConnectOptions();
                System.out.println("Connecting to broker: " + broker);

                sign = MacSignature.macSignature(clientId.split("@@@")[0], secretKey);
                final String[] topicFilters=new String[]{topic+"/notice/"};
                final int[]qos={1};
                connOpts.setUserName(acessKey);
                connOpts.setServerURIs(new String[] { broker });
                connOpts.setPassword(sign.toCharArray());
                connOpts.setCleanSession(false);//设置确定是否继续接受离线消息
                connOpts.setKeepAliveInterval(90);
                connOpts.setAutomaticReconnect(true);
                final ExecutorService executorService = new ThreadPoolExecutor(1, 1, 0, TimeUnit.MILLISECONDS,
                        new LinkedBlockingQueue<Runnable>());
                sampleClient.setCallback(new MqttCallbackExtended() {
                    public void connectComplete(boolean reconnect, String serverURI) {
                        System.out.println("connect success");
                        //连接成功,需要上传客户端所有的订阅关系
                        executorService.submit(new Runnable()
                        {
                            public void run()
                            {
                                try
                                {
                                    sampleClient.subscribe(topicFilters, qos);
                                } catch(Exception me)
                                {
                                    me.printStackTrace();
                                }
                            }
                        });
                    }
                    public void connectionLost(Throwable throwable) {
                        System.out.println("mqtt connection lost");
                    }
                    public void messageArrived(String topic, MqttMessage mqttMessage) throws Exception {
                        System.out.println("messageArrived:" + topic + "------" + new String(mqttMessage.getPayload()));
                    }
                    public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
                        System.out.println("deliveryComplete:" + iMqttDeliveryToken.getMessageId());
                    }
                });
                //客户端每次上线都必须上传自己所有涉及的订阅关系,否则可能会导致消息接收延迟
                sampleClient.connect(connOpts);
                //每个客户端最多允许存在30个订阅关系,超出限制可能会丢弃导致收不到部分消息
                sampleClient.subscribe(topicFilters,qos);
                Thread.sleep(Integer.MAX_VALUE);
            } catch (Exception me) {
                me.printStackTrace();
            }
        }
}

特别注意:

离线消息生成需要一定的时间,因为推送的消息需要等待客户端的 ack 超时才会被判成离线消息,所以获取离线消息一般也需要订阅端等待一定的时间。

参考链接

微消息队列名词解释

MQTT 获取离线消息

在SpringBoot中获取MQTT消息,可以通过使用Spring Integration和Eclipse Paho客户端库来实现。 首先,确保您的项目中已经添加了Spring Integration和Eclipse Paho的依赖。 然后,在您的SpringBoot应用程序中,创建一个配置类,用于配置MQTT连接和消息监听器。在配置类中,您可以使用`@Configuration`注解来标识这是一个配置类,并使用`@Bean`注解来创建一个MQTT连接工厂和一个消息监听器容器。 在创建MQTT连接工厂时,您需要设置MQTT服务器的连接信息,例如服务器地址、端口号和客户端ID等。您还可以设置一些其他的属性,如用户名、密码和是否使用SSL等。 在配置消息监听器容器时,您需要指定要订阅的主题和消息到达时的处理方法。可以使用`@Service`注解将处理方法标识为一个Spring的服务。 下面是一个示例配置类的代码: ```java @Configuration public class MqttConfig { @Value("${mqtt.broker.url}") private String brokerUrl; @Value("${mqtt.broker.username}") private String username; @Value("${mqtt.broker.password}") private String password; @Value("${mqtt.client.id}") private String clientId; @Value("${mqtt.default.topic}") private String defaultTopic; @Bean public MqttConnectOptions mqttConnectOptions() { MqttConnectOptions options = new MqttConnectOptions(); options.setServerURIs(new String[]{brokerUrl}); options.setUserName(username); options.setPassword(password.toCharArray()); options.setCleanSession(true); return options; } @Bean public MqttPahoClientFactory mqttClientFactory() { DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory(); factory.setConnectionOptions(mqttConnectOptions()); return factory; } @Bean public MessageChannel mqttInputChannel() { return new DirectChannel(); } @Bean public MessageProducer mqttInbound() { MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter(clientId, mqttClientFactory(), defaultTopic); adapter.setOutputChannel(mqttInputChannel()); return adapter; } @ServiceActivator(inputChannel = "mqttInputChannel") public void handleMessage(Message<String> message) { // 处理收到的MQTT消息 String payload = message.getPayload(); // 具体的业务逻辑处理 } } ``` 在上述示例中,我们创建了一个`MqttPahoMessageDrivenChannelAdapter`来监听MQTT消息,并将收到的消息发送到`mqttInputChannel`通道。然后,我们使用`@ServiceActivator`注解将`handleMessage`方法标识为一个消息处理方法,通过`message`参数获取到收到的MQTT消息的内容。 您可以根据实际需求配置更多的属性和监听器,以满足您的业务需求。 请注意,上述示例中的配置信息可以放在配置文件中,并通过`@Value`注解注入到配置类中。 通过以上配置,您就能够在SpringBoot应用程序中获取MQTT消息并进行相应的处理。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [物联网>SpringBoot后台客户端获取MQTT消息并保存到数据库(EMQ X Rule Engine规则引擎)](https://blog.csdn.net/weixin_42426714/article/details/113383494)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值