物联网 基于netty构建mqtt协议规范(遗嘱与保留消息)

物联网 基于netty构建mqtt协议规范(遗嘱与保留消息)

简述

遗嘱消息(Last Will) 和 保留消息(Retained Message) 是两个重要的特性

  • 遗嘱消息

客户端异常断开(非正常发送 DISCONNECT)时,Broker 自动发布一条预定义的消息到指定主题,通知其他客户端

  • 保留消息

发布者可以标记消息为"保留",Broker 为每个主题存储最新的保留消息。新订阅该主题的客户端会立即收到这条保留消息

源码(netty-sample-03-Last)

https://gitee.com/kcnf-iot/iot-sample/tree/master/netty/netty-sample-03

要点总结

特性 触发条件 存储位置 推送时机
遗嘱消息 客户端异常断开(Channel inactive 且未发送 DISCONNECT) 内存中关联 Channel 异常断开时立即发布
保留消息 PUBLISH 命令中 retain=1 ConcurrentHashMap<String, String> 新订阅时立即推送(若存在)

server代码

复制代码
package com.jysemel.iot;

import com.jysemel.iot.handler.BrokerHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import java.nio.charset.StandardCharsets;

public class WillRetainBroker {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new LineBasedFrameDecoder(1024));
                            pipeline.addLast(new StringDecoder(StandardCharsets.UTF_8));
                            pipeline.addLast(new StringEncoder(StandardCharsets.UTF_8));
                            pipeline.addLast(new BrokerHandler());
                        }
                    });

            ChannelFuture future = bootstrap.bind(8888).sync();
            System.out.println("╔════════════════════════════════════╗");
            System.out.println("║   MQTT Broker 已启动               ║");
            System.out.println("╠════════════════════════════════════╣");
            System.out.println("║  端口: 8888                        ║");
            System.out.println("║  支持命令:                         ║");
            System.out.println("║    • CONNECT - 连接(设置遗嘱)    ║");
            System.out.println("║    • SUB     - 订阅主题            ║");
            System.out.println("║    • PUB     - 发布消息            ║");
            System.out.println("║    • DISCONNECT - 正常断开         ║");
            System.out.println("║  核心功能:                         ║");
            System.out.println("║    ✓ 遗嘱消息(异常断开触发)      ║");
            System.out.println("║    ✓ 保留消息(新订阅者立即可见)  ║");
            System.out.println("╚════════════════════════════════════╝");
            future.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

client代码

复制代码
package com.jysemel.iot;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

import java.nio.charset.StandardCharsets;

public class WillRetainClient {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup group = new NioEventLoopGroup();

        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ChannelPipeline p = ch.pipeline();
                            p.addLast(new LineBasedFrameDecoder(1024));
                            p.addLast(new StringDecoder(StandardCharsets.UTF_8));
                            p.addLast(new StringEncoder(StandardCharsets.UTF_8));
                        }
                    });

            System.out.println("╔════════════════════════════════════╗");
            System.out.println("║   MQTT 发布者客户端(演示遗嘱+保留) ║");
            System.out.println("╚════════════════════════════════════╝");
            System.out.println("🔌 正在连接 Broker...");

            Channel channel = bootstrap.connect("127.0.0.1", 8888).sync().channel();
            System.out.println("✅ 连接成功!\n");

            Thread.sleep(1000);

            System.out.println("━━━━━━━━━━━━ 步骤1: 设置遗嘱消息 ━━━━━━━━━━━━");
            System.out.println("📝 遗嘱主题: /device/status");
            System.out.println("📝 遗嘱内容: 设备离线(异常断开时触发)");
            channel.writeAndFlush("CONNECT /device/status 设备离线\n").sync();
            Thread.sleep(2000);

            System.out.println("\n━━━━━━━━━━━━ 步骤2: 发布普通消息 ━━━━━━━━━━━━");
            channel.writeAndFlush("PUB /device/status 设备在线\n").sync();
            System.out.println("📤 已发布: 设备在线");
            Thread.sleep(1500);

            System.out.println("\n━━━━━━━━━━━━ 步骤3: 发布保留消息 ━━━━━━━━━━━━");
            System.out.println("💡 特点: 新订阅者会立即收到此消息");
            channel.writeAndFlush("PUB /device/status 温度:25℃ RETAINED\n").sync();
            System.out.println("📤 已发布: 温度:25℃ [保留]");
            Thread.sleep(3000);

            System.out.println("\n━━━━━━━━━━━━ 测试说明 ━━━━━━━━━━━━");
            System.out.println("✅ 现在启动 SubscriberClient,会立即收到【保留消息】");
            System.out.println("⚠️  按 Ctrl+C 强制关闭本程序,将触发【遗嘱消息】");
            System.out.println("💡 遗嘱消息会发送给所有订阅了 /device/status 的客户端\n");

            channel.closeFuture().sync();

        } finally {
            group.shutdownGracefully();
        }
    }
}


package com.jysemel.iot;

import com.jysemel.iot.handler.ClientHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import java.nio.charset.StandardCharsets;

public class SubscriberClient {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup group = new NioEventLoopGroup();

        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ChannelPipeline p = ch.pipeline();
                            p.addLast(new LineBasedFrameDecoder(1024));
                            p.addLast(new StringDecoder(StandardCharsets.UTF_8));
                            p.addLast(new StringEncoder(StandardCharsets.UTF_8));
                            p.addLast(new ClientHandler());
                        }
                    });

            System.out.println("╔════════════════════════════════════╗");
            System.out.println("║   MQTT 订阅者客户端                 ║");
            System.out.println("╚════════════════════════════════════╝");
            System.out.println("🔌 正在连接 Broker...");

            Channel channel = bootstrap.connect("127.0.0.1", 8888).sync().channel();
            System.out.println("✅ 连接成功!\n");

            Thread.sleep(500);

            System.out.println("📋 订阅主题: /device/status");
            System.out.println("💡 如果该主题有保留消息,将立即收到\n");
            channel.writeAndFlush("SUB /device/status\n");

            System.out.println("⏳ 等待接收消息...\n");
            System.out.println("💡 提示:");
            System.out.println("   1. 先运行 WillRetainClient 发布保留消息");
            System.out.println("   2. 再运行本程序,会立即收到保留消息");
            System.out.println("   3. 强制关闭 WillRetainClient 可看到遗嘱消息\n");

            channel.closeFuture().sync();

        } finally {
            group.shutdownGracefully();
        }
    }
}

验证结果

相关推荐
知无不研36 分钟前
lambda表达式的使用(3)
开发语言·c++·lambda
数智化管理手记1 小时前
海量数据如何沉淀有效数据资产?数据标准化治理方案如何搭建?
java·大数据·人工智能
cfm_29141 小时前
SpringAI + Ollama 本地大模型
java·开发语言·人工智能·语言模型
鲸采云SRM采购管理系统2 小时前
采购管理系统哪家好?2026最新测评对比
java·服务器·数据库
C++ 老炮儿的技术栈2 小时前
基于Qt实现轻量化本地音乐播放器
开发语言·c++·qt·c·播放器·音乐
qq_448011162 小时前
C语言中的柔性数组
c语言·开发语言·柔性数组
小白学大数据2 小时前
长周期爬虫的数据一致性:断点续爬 + 事务回滚保障采集质量
开发语言·爬虫·测试工具
0x533 小时前
Java网络编程(二)
java·服务器·网络
zlinear数据采集卡3 小时前
D223上位机C#开发实战:从帧解析到波形显示的完整实现
开发语言·arm开发·嵌入式硬件·fpga开发·开源·c#
Flittly3 小时前
【雕虫大技】Agent 动态 Skill 供应链安全加固(三):输出校验与治理闭环实战
java·spring boot·spring