Netty服務端主動給客戶端發送消息並接收客戶端返回的消息

在使用Netty服務端做消息接收服務時,有時客戶端設備不會直接給服務端發送消息,需要先向客戶端發送讀取消息的命令,然後客戶端纔會給服務端返回消息,本例中就是實現這個操作。

過程描述如下:

1、客戶端設備在首次與服務端建立連接時,將此連接的通道保存下來,存入線程安全的ConcurrentHashMap中。

2、定時遍歷ConcurrentHashMap,如果連接是存活狀態,就通過此通道發送消息給客戶端,如果不是存活狀態,就從map中刪除此通道信息。

3、正常接收客戶端傳來的信息。

 

public class NettyChannelService {
    private static ConcurrentHashMap<String, ChannelHandlerContext> map = new ConcurrentHashMap<>();

    public static Map<String, ChannelHandlerContext> getChannels() {
        return map;
    }

    public static void saveChannel(String key, ChannelHandlerContext ctx) {
        if (map == null) {
            map = new ConcurrentHashMap<>();
        }
        map.put(key, ctx);
    }

    public static ChannelHandlerContext getChannel(String key) {
        if (map == null || map.isEmpty()) {
            return null;
        }
        return map.get(key);
    }

    public static void removeChannel(String key) {
        map.remove(key);
    }
}
@Component
public class NettyServerStartRollerCone implements CommandLineRunner {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    @Value("${tcpServerPort.rollerCone}")
    private int port;

    private Environment environment = SpringUtils.getBean(Environment.class);
    private String frequencyMin = environment.getProperty("rollerCone.frequency_min", String.class);

    @Override
    public void run(String... args) {
        // netty服務器初始化
        Executors.newSingleThreadExecutor().submit(new NettyServerRollerCone());
        // 定期向客戶端發送請求
        Executors.newSingleThreadExecutor().submit(new NettyTaskRollerCone());
    }

    class NettyServerRollerCone implements Runnable {
        @Override
        public void run() {
            logger.info("NettyServerRollerCone is beginning...");
            // 創建線程組BossGroup,處理連接請求
            EventLoopGroup bossGroup = new NioEventLoopGroup(1);
            // 創建線程組workerGroup,完成和客戶端具體的業務處理
            // 無參:則workerGroup含有的子線程個數=NettyRuntime.availableProcessors() * 2,即CPU核數*2
            EventLoopGroup workerGroup = new NioEventLoopGroup(2);
            // 創建服務器端啓動對,用來配置參數
            ServerBootstrap bootstrap = new ServerBootstrap();
            try {
                // 設置兩個線程組
                bootstrap.group(bossGroup, workerGroup);
                // 使用NioServerSocketChannel作爲服務器的通道
                bootstrap.channel(NioServerSocketChannel.class);
                // 設置線程隊列得到連接個數
                bootstrap.option(ChannelOption.SO_BACKLOG, 1024);
                // 允許重複使用本地地址和端口
                bootstrap.option(ChannelOption.SO_REUSEADDR, true);
                // 當SO_KEEPALIVE=true的時候,服務端可以探測客戶端的連接是否還存活着,如果客戶端關閉了,那麼服務端的連接可以關閉掉,釋放資源
                bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);

                bootstrap.childHandler(
                        new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel ch) {
                                ChannelPipeline pipeline = ch.pipeline();
                                pipeline.addLast("decoder", new NettyServerDecoder());
                                pipeline.addLast(new StringEncoder());
                                pipeline.addLast(new NettyServerHandlerRollerCone());
                            }
                        });
                ChannelFuture future;
                try {
                    //綁定一個端口並啓動服務器,生成一個ChannelFuture對象 綁定:bind
                    future = bootstrap.bind(port).sync();
                    //對關閉通道進行監聽
                    future.channel().closeFuture().sync();
                } catch (InterruptedException e) {
                    logger.info("NettyServerStartRollerCone 數據異常 -> " + e.getMessage());
                }
            } finally {
                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
            }
        }
    }

    class NettyTaskRollerCone implements Runnable {
        @Override
        @SuppressWarnings("InfiniteLoopStatement")
        public void run() {
            logger.info("NettyTaskRollerCone is beginning...");
            while (true) {
                try {
                    for (String key : NettyChannelService.getChannels().keySet()) {
                        ChannelHandlerContext ctx = NettyChannelService.getChannel(key);
                        if (ctx == null) {
                            continue;
                        }
                        if (ctx.channel().isActive()) {
                            String reqStr = "此處是向客戶端發送的命令";
                            byte[] reqStrBytes = getHexBytes(reqStr);
                            ByteBuf reqStrByteBuf = ctx.alloc().buffer(reqStrBytes.length);
                            reqStrByteBuf.writeBytes(reqStrBytes);
                            ctx.writeAndFlush(reqStrByteBuf);
                            logger.info("發送請求 --> 請求地址: " + ctx.channel().remoteAddress() + " channel id: " + key);
                            logger.info("發送請求 --> 請求數據: " + reqStr);
                            // 這裏暫停一下是防止channelRead收到的數據粘包
                            Thread.sleep(1000);
                        } else {
                            NettyChannelService.removeChannel(key);
                        }
                    }
                    Thread.sleep(Integer.parseInt(frequencyMin) * 60 * 1000);
                } catch (Exception e) {
                    logger.error("NettyTaskRollerCone發生異常: " + e.getMessage());
                }
            }
        }
    }

    public byte[] getHexBytes(String str) {
        str = str.replaceAll(" ", "");
        byte[] bytes = new byte[str.length() / 2];
        for (int i = 0; i < str.length() / 2; i++) {
            String subStr = str.substring(i * 2, i * 2 + 2);
            bytes[i] = (byte) Integer.parseInt(subStr, 16);
        }
        return bytes;
    }
}
@Component
public class NettyServerHandlerRollerCone extends ChannelInboundHandlerAdapter {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        String uuid = ctx.channel().id().asLongText();
        NettyChannelService.saveChannel(uuid, ctx);
        logger.info("連接請求進入: " + uuid + " 地址: " + ctx.channel().remoteAddress());
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        logger.info("返回數據 --> " + msg);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章