springboot+netty+mqtt客户端实现

前言

相对于服务端,客户端稍微简单一些,因为它不需要关心Retain(是否需要缓存消息)、连接、管理订阅者等这些费时费力的事情,服务端的话可以参考上一篇文章:springboot+netty+mqtt服务端实现

客户端实现

启动类

/**
 * @author: zhouwenjie
 * @description: 客户端
 * @create: 2020-04-03 17:14
 **/
@Slf4j
@Component
public class MqttClient {

    @Value("${driver.mqtt.server_host}")
    private String hostServer;

    @Value("${driver.mqtt.server_port}")
    private int portServer;

    @Autowired
    private ClientMqttHandler clientMqttHandler;

    private NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup();

    private Bootstrap bootstrap;

    public void run() {
        bootstrap = new Bootstrap();
        bootstrap.group(eventLoopGroup)
                .channel(NioSocketChannel.class)
                .option(ChannelOption.TCP_NODELAY, true)
                .option(ChannelOption.SO_KEEPALIVE, true)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) {
                        //客户端初始化
                        socketChannel.pipeline().addLast("decoder", new MqttDecoder(1024 * 8));
                        socketChannel.pipeline().addLast("encoder", MqttEncoder.INSTANCE);
                        socketChannel.pipeline().addLast(clientMqttHandler);
                    }
                });
        //连接netty服务器
        reconnect(hostServer, portServer);
    }

    /**
     * 功能描述: 断线重连,客户端有断线重连机制,就更不能使用异步阻塞了
     *
     * @param
     * @return void
     * @author zhouwenjie
     * @date 2021/3/19 14:53
     */
    public void reconnect(String host, Integer port) {
        bootstrap.remoteAddress(host, port);
        ChannelFuture channelFuture = bootstrap.connect();
        //使用最新的ChannelFuture -> 开启最新的监听器
        channelFuture.addListener((ChannelFutureListener) future -> {
            if (future.cause() != null) {
                log.error("MQTT驱动服务端" + host + ":" + port + "连接失败。。。");
                future.channel().eventLoop().schedule(() -> reconnect(host, port), 3, TimeUnit.SECONDS);
            } else {
                log.info("MQTT驱动服务端" + host + ":" + port + "连接成功");
            }
        });
    }

    /**
     * 关闭 client
     */
    @PreDestroy
    public void shutdown() {
        // 优雅关闭 EventLoopGroup 对象
        eventLoopGroup.shutdownGracefully();
        log.info("[*MQTT客户端关闭]");
    }
}

handler处理类

/**
 * @author: zhouwenjie
 * @description: 客户端处理类
 * @create: 2020-04-03 17:45
 * <p>
 **/
@Component
@Slf4j
@ChannelHandler.Sharable
public class ClientMqttHandler extends SimpleChannelInboundHandler<MqttMessage> {

    /**
     * 注入NettyClient
     */
    @Autowired
    private MqttClient mqttClient;

    @Value("${driver.mqtt.server_host}")
    private String hostServer;

    @Value("${driver.mqtt.server_port}")
    private int portServer;

    public static ChannelHandlerContext context;

    @Autowired
    private MqttMsgBack mqttMsgBack;

    /**
     * 连接成功
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        InetSocketAddress ipSocket = (InetSocketAddress) ctx.channel().remoteAddress();
        int port = ipSocket.getPort();
        String host = ipSocket.getHostString();
        if (hostServer.equals(host) && port == portServer) {
            context = ctx;
            mqttMsgBack.connect(ctx);
        }
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        InetSocketAddress ipSocket = (InetSocketAddress) ctx.channel().remoteAddress();
        int port = ipSocket.getPort();
        String host = ipSocket.getHostString();
        log.error("与设备" + host + ":" + port + "连接断开!" + "断开定时发送");
        ctx.close();
        ctx.deregister();
        ctx.pipeline().remove(this);
        super.channelInactive(ctx);
        mqttClient.reconnect(ipSocket.getHostString(), ipSocket.getPort());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
        log.error("[* Netty connection exception]:{}", cause.toString());
        cause.printStackTrace();
    }

    @Override
    public void channelRead0(ChannelHandlerContext ctx, MqttMessage mqttMessage) {
        if (null != mqttMessage) {
            log.info("接收mqtt消息:" + mqttMessage);
            MqttFixedHeader mqttFixedHeader = mqttMessage.fixedHeader();
            switch (mqttFixedHeader.messageType()) {
                // ----------------------发送消息端(客户端)可能会触发的事件----------------------------------------------------------------
                case CONNACK:
                    mqttMsgBack.receiveConnectionAck(ctx, mqttMessage);
                    break;
                case PUBREC:
                case PUBACK:
                    //接收服务端的ack消息
                    mqttMsgBack.receivePubAck(ctx, mqttMessage);
                    break;
                case PUBCOMP:
                    mqttMsgBack.receivePubcomp(ctx, mqttMessage);
                    break;
                case SUBACK:
                    mqttMsgBack.receiveSubAck(ctx, mqttMessage);
                    break;
                case UNSUBACK:
                    mqttMsgBack.receiveUnSubAck(ctx, mqttMessage);
                    break;
                case PINGRESP:
                    //客户端发起心跳
                    mqttMsgBack.pingReq(ctx, mqttMessage);
                    break;
                // ----------------------接收消息端(客户端)可能会触发的事件----------------------------------------------------------------
                case PUBLISH:
                    //	收到消息,返回确认,PUBACK报文是对QoS 1等级的PUBLISH报文的响应,PUBREC报文是对PUBLISH报文的响应
                    mqttMsgBack.publishAck(ctx, mqttMessage);
                    break;
                case PUBREL:
                    //	释放消息,PUBREL报文是对QoS 2等级的PUBREC报文的响应,此时我们应该回应一个PUBCOMP报文
                    mqttMsgBack.publishComp(ctx, mqttMessage);
                    break;
                default:
                    break;
            }
        }
    }
}

消息处理方法类

/**
 * @author: zhouwenjie
 * @description: 对接收到的消息进行业务处理
 * @create: 2023-04-07 16:29
 * CONNECT	    1	    C->S	客户端请求与服务端建立连接 (服务端接收)
 * CONNACK	    2	    S->C	服务端确认连接建立(客户端接收)
 * PUBLISH	    3	    CóS	    发布消息 (服务端接收【QoS 0级别,最多分发一次】)-->生产者只会发送一次消息,不关心消息是否被代理服务端或消费者收到
 * PUBACK	    4	    CóS	    收到发布消息确认(客户端接收【QoS 1级别,至少分发一次】) -->保证消息发送到服务端(也就是代理服务器broker),如果没收到或一定时间没收到服务端的ack,就会重发消息
 * PUBREC	    5	    CóS	    收到发布消息(客户端接收【QoS 2级别】)|
 * PUBREL	    6	    CóS	    释放发布消息(服务端接收【QoS 2级别】)|只分发一次消息,且保证到达 -->这三步保证消息有且仅有一次传递给消费者
 * PUBCOMP	    7	    CóS	    完成发布消息(客户端接收【QoS 2级别】)|
 * SUBSCRIBE	8	    C->S	订阅请求(服务端接收)
 * SUBACK	    9	    S->C	订阅确认(客户端接收)
 * UNSUBSCRIBE	10	    C->S	取消订阅(服务端接收)
 * UNSUBACK	    11	    S->C	取消订阅确认(客户端接收)
 * PINGREQ	    12	    C->S	客户端发送PING(连接保活)命令(服务端接收)
 * PINGRESP	    13	    S->C	PING命令回复(客户端接收)
 * DISCONNECT	14	    C->S	断开连接 (服务端接收)
 **/

@Slf4j
@Component
public class MqttMsgBack {

    @Value("${driver.mqtt.wait_time}")
    private long waitTime;

    @Value("${driver.mqtt.topic_name}")
    private String defaultTopicName;

    @Value("${driver.mqtt.user_name}")
    private String userName;

    @Value("${driver.mqtt.password}")
    private String password;

    // 记录消息id的变量,id值范围1~65535
    private final AtomicInteger nextMessageId = new AtomicInteger(1);

    // ----------------------发送消息端(客户端)可能使用的方法----------------------------------------------------------------

    /**
     * 确认连接请求
     *
     * @param ctx
     * @param mqttMessage
     */
    public void receiveConnectionAck(ChannelHandlerContext ctx, MqttMessage mqttMessage) {
        MqttConnAckMessage mqttConnAckMessage = (MqttConnAckMessage) mqttMessage;
        MqttConnAckVariableHeader variableHeader = mqttConnAckMessage.variableHeader();
        MqttConnectReturnCode mqttConnectReturnCode = variableHeader.connectReturnCode();
        if (mqttConnectReturnCode.name().equals(MqttConnectReturnCode.CONNECTION_ACCEPTED.name())) {
            //连接成功
            log.info("服务端连接验证成功");
            // 订阅主题
            this.subscribe(defaultTopicName, MqttQoS.AT_MOST_ONCE);
        } else {
            log.error("服务端连接验证失败:" + mqttConnectReturnCode.name());
        }
    }

    /**
     * 确认订阅回复
     *
     * @param ctx
     * @param mqttMessage
     */
    public void receiveSubAck(ChannelHandlerContext ctx, MqttMessage mqttMessage) {
        //删除消息重发机制
        MqttMessageIdVariableHeader variableHeader = (MqttMessageIdVariableHeader) mqttMessage.variableHeader();
        int messageId = variableHeader.messageId();
        ScheduledFuture<?> scheduledFuture = TimerData.scheduledFutureMap.remove(messageId);
        if (scheduledFuture != null) {
            scheduledFuture.cancel(true);
        }
    }

    /**
     * 确认取消订阅回复
     *
     * @param ctx
     * @param mqttMessage
     */
    public void receiveUnSubAck(ChannelHandlerContext ctx, MqttMessage mqttMessage) {
        //删除消息重发机制
        MqttMessageIdVariableHeader variableHeader = (MqttMessageIdVariableHeader) mqttMessage.variableHeader();
        int messageId = variableHeader.messageId();
        ScheduledFuture<?> scheduledFuture = TimerData.scheduledFutureMap.remove(messageId);
        if (scheduledFuture != null) {
            scheduledFuture.cancel(true);
        }
    }

    /**
     * 根据qos发布确认
     *
     * @param ctx
     * @param mqttMessage
     */
    public void receivePubAck(ChannelHandlerContext ctx, MqttMessage mqttMessage) {
        MqttFixedHeader fixedHeader = mqttMessage.fixedHeader();
        MqttMessageType messageType = fixedHeader.messageType();
        if (messageType == PUBACK) {
            MqttMessageIdVariableHeader variableHeader = (MqttMessageIdVariableHeader) mqttMessage.variableHeader();
            int messageId = variableHeader.messageId();
            //等级为1的情况,直接删除原始消息,取消消息重发机制
            ScheduledFuture<?> scheduledFuture = TimerData.scheduledFutureMap.remove(messageId);
            if (scheduledFuture != null) {
                scheduledFuture.cancel(true);
            }
        }
        if (messageType == PUBREC) {
            //等级为2的情况,收到PUBREC报文消息,先停止消息重发机制,再响应一个PUBREL报文并且构建消息重发机制
            MqttPubReplyMessageVariableHeader variableHeader = (MqttPubReplyMessageVariableHeader) mqttMessage.variableHeader();
            int messageId = variableHeader.messageId();
            //构建返回报文,固定报头
            MqttFixedHeader mqttFixedHeaderBack = new MqttFixedHeader(MqttMessageType.PUBREL, false, AT_LEAST_ONCE, false, 0);
            //构建返回报文,可变报头
            MqttPubReplyMessageVariableHeader mqttPubReplyMessageVariableHeader = new MqttPubReplyMessageVariableHeader(messageId, MqttPubReplyMessageVariableHeader.REASON_CODE_OK, MqttProperties.NO_PROPERTIES);
            MqttMessage mqttMessageBack = new MqttMessage(mqttFixedHeaderBack, mqttPubReplyMessageVariableHeader);
            ctx.writeAndFlush(mqttMessageBack);
            //删除初始消息重发机制
            ScheduledFuture<?> scheduledFuture = TimerData.scheduledFutureMap.remove(messageId);
            if (scheduledFuture != null) {
                scheduledFuture.cancel(true);
            }
            ctx.writeAndFlush(mqttMessageBack).addListener(future -> {
                //构建消息重发
                cachePubrelMsg(messageId, ctx);
            });
        }
    }

    private void cachePubrelMsg(int messageId, ChannelHandlerContext context) {
        //缓存一份消息,规定时间内没有收到ack,用作重发,重发时将isDup设置为true,代表重复消息
        //构建返回报文,固定报头
        MqttFixedHeader mqttFixedHeaderBack = new MqttFixedHeader(MqttMessageType.PUBREL, false, AT_LEAST_ONCE, false, 0);
        //构建返回报文,可变报头
        MqttMessageIdVariableHeader mqttMessageIdVariableHeaderBack = MqttMessageIdVariableHeader.from(messageId);
        MqttMessage mqttMessageBack = new MqttMessage(mqttFixedHeaderBack, mqttMessageIdVariableHeaderBack);
        ScheduledFuture<?> scheduledFuture = TimerData.scheduledThreadPoolExecutor.scheduleAtFixedRate(new MonitorMsgTime(messageId, mqttMessageBack, context), waitTime, waitTime, TimeUnit.MILLISECONDS);
        TimerData.scheduledFutureMap.put(messageId, scheduledFuture);
    }

    /**
     * 功能描述: 接收到最后一次确认,取消上次PUBREL的消息重发机制
     *
     * @param ctx
     * @param mqttMessage
     * @return void
     * @author zhouwenjie
     * @date 2023/6/9 16:00
     */
    public void receivePubcomp(ChannelHandlerContext ctx, MqttMessage mqttMessage) {
        MqttPubReplyMessageVariableHeader variableHeader = (MqttPubReplyMessageVariableHeader) mqttMessage.variableHeader();
        int messageId = variableHeader.messageId();
        ScheduledFuture<?> scheduledFuture = TimerData.scheduledFutureMap.remove(messageId);
        if (scheduledFuture != null) {
            scheduledFuture.cancel(true);
        }
    }

    /**
     * 心跳发送
     *
     * @param ctx
     * @param mqttMessage
     */
    public void pingReq(ChannelHandlerContext ctx, MqttMessage mqttMessage) {
        if (ctx != null && ctx.channel().isActive()) {
            MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PINGREQ, false, MqttQoS.AT_MOST_ONCE, false, 0);
            MqttMessage mqttMessageBack = new MqttMessage(fixedHeader);
            ctx.writeAndFlush(mqttMessageBack);
        } else {
            log.error("心跳提醒:服务端连接异常~");
        }
    }


    public void connect(ChannelHandlerContext ctx) {
        MqttConnectVariableHeader mqttConnectVariableHeader = new MqttConnectVariableHeader("MQTT", 4, true, true, false, 0, false, true, 60);
        String uuid = UUID.randomUUID().toString().replace("-", "");
        MqttConnectPayload connectPayload = new MqttConnectPayload(uuid, null, null, userName, password.getBytes(CharsetUtil.UTF_8));
        MqttFixedHeader mqttFixedHeaderInfo = new MqttFixedHeader(MqttMessageType.CONNECT, false, MqttQoS.AT_LEAST_ONCE, false, 0);
        MqttConnectMessage connectMessage = new MqttConnectMessage(mqttFixedHeaderInfo, mqttConnectVariableHeader, connectPayload);
        ctx.writeAndFlush(connectMessage);
    }

    /**
     * 主动发送消息
     *
     * @param topic   :主题名称
     * @param payload :消息体
     * @param qos     : 服务质量等级
     * @param retain  :
     *                true:表示发送的消息需要一直持久保存(不受服务器重启影响),不但要发送给当前的订阅者,并且以后新来的订阅了此Topic name的订阅者会马上得到推送。
     *                false:仅仅为当前订阅者推送此消息。
     */
    public void publish(String topic, ByteBuf payload, MqttQoS qos, boolean retain) {
        MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH, false, qos, retain, 0);
        MqttPublishVariableHeader variableHeader = new MqttPublishVariableHeader(topic, getNewMessageId().messageId());
        MqttPublishMessage mqttPublishMessage = new MqttPublishMessage(fixedHeader, variableHeader, payload);
        //将消息发送给订阅的客户端
        ChannelHandlerContext context = ClientMqttHandler.context;
        if (context != null && context.channel().isActive()) {
            //因为ByteBuf每次发送之后就会被清空了,下次发送就拿不到payload,所以提前复制一份,客户端这里不用,因为在调用此方法的时候已经调用了Unpooled.wrappedBuffer了
            payload.retainedDuplicate();
            context.writeAndFlush(mqttPublishMessage);
            if (qos == AT_LEAST_ONCE || qos == EXACTLY_ONCE) {
                cachePublishMsg(qos, payload, variableHeader, fixedHeader, context);
            }
        }else {
            log.error("发送消息提醒:服务端连接异常~");
        }
    }

    private void cachePublishMsg(MqttQoS qos, ByteBuf byteBuf, MqttPublishVariableHeader variableHeader, MqttFixedHeader mqttFixedHeaderInfo, ChannelHandlerContext context) {
        //缓存一份消息,规定时间内没有收到ack,用作重发,重发时将isDup设置为true,代表重复消息
        MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH, true, qos, false, mqttFixedHeaderInfo.remainingLength());
        MqttPublishMessage cachePubMessage = new MqttPublishMessage(fixedHeader, variableHeader, byteBuf);
        ScheduledFuture<?> scheduledFuture = TimerData.scheduledThreadPoolExecutor.scheduleAtFixedRate(new MonitorMsgTime(variableHeader.packetId(), cachePubMessage, context), waitTime, waitTime, TimeUnit.MILLISECONDS);
        TimerData.scheduledFutureMap.put(variableHeader.packetId(), scheduledFuture);
    }

    /**
     * 订阅主题
     *
     * @param topicName :主题名称
     * @param qos       :服务端可以向此客户端发送的应用消息的最大QoS等级
     */
    public void subscribe(String topicName, MqttQoS qos) {
        if (StrUtil.isBlank(topicName)) {
            topicName = defaultTopicName;
        }
        //构造固定头
        MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.SUBSCRIBE, false, AT_LEAST_ONCE, false, 0);
        MqttMessageIdVariableHeader variableHeader = getNewMessageId();
        //构造消息体,这里构建采用简单的模式(MqttTopicSubscription(String topicFilter, MqttQoS qualityOfService))
        // 如果想用更复杂的,使用(MqttTopicSubscription(String topicFilter, MqttSubscriptionOption option))
        MqttTopicSubscription subscription = new MqttTopicSubscription(topicName, qos);
        MqttSubscribePayload payload = new MqttSubscribePayload(Collections.singletonList(subscription));
        MqttSubscribeMessage mqttSubscribeMessage = new MqttSubscribeMessage(fixedHeader, variableHeader, payload);
        ChannelHandlerContext context = ClientMqttHandler.context;
        if (context != null && context.channel().isActive()) {
            //发送消息,异步发送
            context.writeAndFlush(mqttSubscribeMessage);
            //缓存消息
            MqttFixedHeader fixedHeader2 = new MqttFixedHeader(MqttMessageType.SUBSCRIBE, true, AT_LEAST_ONCE, false, 0);
            MqttSubscribeMessage mqttSubscribeMessage2 = new MqttSubscribeMessage(fixedHeader2, variableHeader, payload);
            ScheduledFuture<?> scheduledFuture = TimerData.scheduledThreadPoolExecutor.scheduleAtFixedRate(new MonitorMsgTime(variableHeader.messageId(), mqttSubscribeMessage2, context), waitTime, waitTime, TimeUnit.MILLISECONDS);
            TimerData.scheduledFutureMap.put(variableHeader.messageId(), scheduledFuture);
        }else {
            log.error("订阅提醒:服务端连接异常~");
        }
    }

    /**
     * 取消订阅主题
     *
     * @param topicName
     */
    public void unsubscribe(String topicName) {
        if (StrUtil.isBlank(topicName)) {
            topicName = defaultTopicName;
        }
        MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.UNSUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 0);
        MqttMessageIdVariableHeader variableHeader = getNewMessageId();
        MqttUnsubscribePayload payload = new MqttUnsubscribePayload(Collections.singletonList(topicName));
        MqttUnsubscribeMessage mqttUnsubscribeMessage = new MqttUnsubscribeMessage(fixedHeader, variableHeader, payload);
        ChannelHandlerContext context = ClientMqttHandler.context;
        if (context != null && context.channel().isActive()) {
            //发送消息
            context.writeAndFlush(mqttUnsubscribeMessage);
            MqttFixedHeader fixedHeader2 = new MqttFixedHeader(MqttMessageType.UNSUBSCRIBE, true, MqttQoS.AT_LEAST_ONCE, false, 0);
            MqttUnsubscribeMessage mqttUnsubscribeMessage2 = new MqttUnsubscribeMessage(fixedHeader2, variableHeader, payload);
            //缓存消息
            ScheduledFuture<?> scheduledFuture = TimerData.scheduledThreadPoolExecutor.scheduleAtFixedRate(new MonitorMsgTime(variableHeader.messageId(), mqttUnsubscribeMessage2, context), waitTime, waitTime, TimeUnit.MILLISECONDS);
            TimerData.scheduledFutureMap.put(variableHeader.messageId(), scheduledFuture);
        }else {
            log.error("取消订阅提醒:服务端连接异常~");
        }
    }

    /**
     * 功能描述: 获取消息id,int数,从1开始不能大于65535
     *
     * @param
     * @return io.netty.handler.codec.mqtt.MqttMessageIdVariableHeader
     * @author zhouwenjie
     * @date 2023/6/12 16:16
     */
    private MqttMessageIdVariableHeader getNewMessageId() {
        int messageId;
        synchronized (this.nextMessageId) {
            this.nextMessageId.compareAndSet(0xffff, 1);
            messageId = this.nextMessageId.getAndIncrement();
        }
        return MqttMessageIdVariableHeader.from(messageId);
    }

    // ----------------------接收消息端(客户端)可能使用的方法----------------------------------------------------------------

    /**
     * 收到publish消息后的确认回复
     * 根据qos发布确认
     * isRetain:发布保留标识,表示服务器要保留这次推送的信息,如果有新的订阅者出现,就把这消息推送给它,如果设有那么推送至当前订阅者后释放
     *
     * @param ctx
     * @param mqttMessage
     */
    public void publishAck(ChannelHandlerContext ctx, MqttMessage mqttMessage) {
        MqttPublishMessage mqttPublishMessage = (MqttPublishMessage) mqttMessage;
        MqttFixedHeader mqttFixedHeaderInfo = mqttPublishMessage.fixedHeader();
        MqttQoS qos = mqttFixedHeaderInfo.qosLevel();
        //返回消息给发送端
        switch (qos) {
            //至多一次
            case AT_MOST_ONCE:
                break;
            //至少一次
            case AT_LEAST_ONCE:
                //构建返回报文, 可变报头
                MqttMessageIdVariableHeader mqttMessageIdVariableHeaderBack = MqttMessageIdVariableHeader.from(mqttPublishMessage.variableHeader().packetId());
                //构建返回报文, 固定报头
                MqttFixedHeader mqttFixedHeaderBack = new MqttFixedHeader(PUBACK, mqttFixedHeaderInfo.isDup(), AT_MOST_ONCE, mqttFixedHeaderInfo.isRetain(), 0x02);
                //构建PUBACK消息体
                MqttPubAckMessage pubAck = new MqttPubAckMessage(mqttFixedHeaderBack, mqttMessageIdVariableHeaderBack);
                ctx.writeAndFlush(pubAck);
                break;
            //刚好一次
            case EXACTLY_ONCE:
                //构建返回报文,固定报头
                MqttFixedHeader mqttFixedHeaderBack2 = new MqttFixedHeader(MqttMessageType.PUBREC, false, AT_MOST_ONCE, false, 0x02);
                //构建返回报文,可变报头
                MqttPubReplyMessageVariableHeader mqttPubReplyMessageVariableHeader = new MqttPubReplyMessageVariableHeader(mqttPublishMessage.variableHeader().packetId(), MqttPubReplyMessageVariableHeader.REASON_CODE_OK, MqttProperties.NO_PROPERTIES);
                MqttMessage mqttMessageBack = new MqttMessage(mqttFixedHeaderBack2, mqttPubReplyMessageVariableHeader);
                ctx.writeAndFlush(mqttMessageBack);
                break;
            default:
                break;
        }
    }

    /**
     * 发布完成 qos2
     *
     * @param ctx
     * @param mqttMessage
     */
    public void publishComp(ChannelHandlerContext ctx, MqttMessage mqttMessage) {
        MqttMessageIdVariableHeader messageIdVariableHeader = (MqttMessageIdVariableHeader) mqttMessage.variableHeader();
        //构建返回报文, 固定报头
        MqttFixedHeader mqttFixedHeaderBack = new MqttFixedHeader(MqttMessageType.PUBCOMP, false, MqttQoS.AT_MOST_ONCE, false, 0x02);
        //构建返回报文, 可变报头
        MqttMessageIdVariableHeader mqttMessageIdVariableHeaderBack = MqttMessageIdVariableHeader.from(messageIdVariableHeader.messageId());
        MqttMessage mqttMessageBack = new MqttMessage(mqttFixedHeaderBack, mqttMessageIdVariableHeaderBack);
        ctx.writeAndFlush(mqttMessageBack);
    }
}

重发机制处理机制

/**
 * @author: zhouwenjie
 * @description: 轮询线程信息存储
 * @create: 2021-04-29 08:06
 **/
public class TimerData {


    public static ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(10);

    public static ConcurrentHashMap<Integer, ScheduledFuture<?>> scheduledFutureMap = new ConcurrentHashMap<>();
}
/**
 * @author: zhouwenjie
 * @description: 判断策略相关消息是否在规定时间段内发送,获取结束状态
 * @create: 2021-01-07 16:09
 **/
@Slf4j
public class MonitorMsgTime implements Runnable {

    private Integer packetId;
    private MqttMessage mqttMessage;
    private ChannelHandlerContext ctx;

    public MonitorMsgTime(Integer packetId, MqttMessage mqttMessage, ChannelHandlerContext ctx) {
        this.packetId = packetId;
        this.mqttMessage = mqttMessage;
        this.ctx = ctx;
    }

    @Override
    public void run() {
        //注意,整个执行过程中,代码报错,线程就会终止
        InetSocketAddress address = (InetSocketAddress) ctx.channel().remoteAddress();
        if (ctx != null && ctx.channel().isActive()) {
            log.info("重复发送消息给服务端:" + address.getHostString());
            if (mqttMessage instanceof MqttPublishMessage) {
                //推送的原始消息,每次推送,都需要重新拷贝一份
                try {
                    MqttPublishMessage mqttPublishMessage = (MqttPublishMessage) mqttMessage;
                    ByteBuf byteBuf = mqttPublishMessage.payload();
                    byteBuf.retainedDuplicate();
                    ctx.writeAndFlush(mqttMessage);
                } catch (Exception e) {
                    e.printStackTrace();
                    throw e;
                }
            } else {
                //回复的ack类型消息
                ctx.writeAndFlush(mqttMessage);
            }
        } else {
            log.error(address.getHostString() + " 服务端断开,结束重复发送");
            //如果离线了,就不发了
            ScheduledFuture<?> scheduledFuture = TimerData.scheduledFutureMap.remove(packetId);
            if (scheduledFuture != null) {
                scheduledFuture.cancel(true);
            }
        }
    }
}
  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
实现心跳保活机制是为了确保网络连接的稳定性和可靠性,防止连接因长时间不活动而被关闭。在Spring BootNetty中,可以通过以下步骤实现心跳保活机制: 1. 创建一个Netty服务器并设置相关参数,如端口号和TCP参数。可以使用Spring Boot提供的`@Configuration`注解和Netty的`ServerBootstrap`类来完成这一步骤。 2. 使用Netty的`ChannelInitializer`类创建一个处理器来处理客户端的请求,并实现`ChannelInboundHandlerAdapter`类的`channelRead`方法。 3. 在处理器的`channelRead`方法中,判断收到的消息是否为心跳消息。可以根据消息内容或自定义的标识来判断是否为心跳消息。 4. 如果接收到的消息是心跳消息,可以通过向客户端发送一个固定的心跳响应消息来维持连接。可以使用Netty的`ctx.writeAndFlush()`方法来发送心跳响应消息。 5. 如果接收到的消息不是心跳消息,可以继续处理其他业务逻辑。 6. 在处理器的`channelInactive`方法中,可以处理连接断开时的逻辑。可以在此方法中关闭连接、释放资源等操作。 7. 在Netty服务器的配置中,设置心跳超时时间。可以使用Netty的`IdleStateHandler`类来实现心跳超时的检测和处理。 8. 在上述步骤完成后,运行Spring Boot应用程序,并使用客户端发送心跳消息来保持连接。可以通过不断发送心跳消息,来确保连接保持活动状态。 通过以上步骤,就可以在Spring BootNetty实现心跳保活机制,确保网络连接的稳定性和可靠性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值