• Netty 群聊实现


        用netty 实现一个群聊,服务端接收客户端消息,并且转发给其他用户。无论是群聊还是单聊,都是由服务器端进行转发。

    1. ChatServer

    package netty.chat;
    
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.channel.*;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.SocketChannel;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    import io.netty.handler.codec.string.StringDecoder;
    import io.netty.handler.codec.string.StringEncoder;
    
    public class ChatServer {
    
        public static void main(String[] args) throws InterruptedException {
            EventLoopGroup bossGroup = new NioEventLoopGroup(1);
            EventLoopGroup workerExecutors = new NioEventLoopGroup();
    
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerExecutors)
                    .channel(NioServerSocketChannel.class) // 设置服务器的通道
                    .option(ChannelOption.SO_BACKLOG, 128) // 设置线程队列得到连接个数
                    .childOption(ChannelOption.SO_KEEPALIVE, true) // 设置保持活动连接状态
                    .childHandler(new ChannelInitializer<SocketChannel>() { // 设置通道测试对象(匿名对象)
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            //向pipeline加入解码器
                            pipeline.addLast("decoder", new StringDecoder());
                            //向pipeline加入编码器
                            pipeline.addLast("encoder", new StringEncoder());
                            // 给pipeline添加一个handler
                            socketChannel.pipeline().addLast(new ChatServerHandler());
                        }
                    });
    
            ChannelFuture sync = serverBootstrap.bind(8989).sync();
            sync.addListener(future -> {
                if (future.isSuccess()) {
                    System.out.println("服务器绑定8989端口成功");
                }
            });
        }
    }

    2. ChatServerHandler

    package netty.chat;
    
    import io.netty.channel.Channel;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.SimpleChannelInboundHandler;
    import io.netty.channel.group.ChannelGroup;
    import io.netty.channel.group.DefaultChannelGroup;
    import io.netty.util.concurrent.GlobalEventExecutor;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    public class ChatServerHandler extends SimpleChannelInboundHandler<String> {
    
        //定义一个channle 组,管理所有的channel。GlobalEventExecutor.INSTANCE) 是全局的事件执行器,是一个单例
        private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
        // 用于格式化日期
        private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
        /**
         * handlerAdded 表示连接建立,一旦连接,第一个被执行。将当前channel 加入到  channelGroup
         */
        @Override
        public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
            Channel channel = ctx.channel();
            // 将该客户加入聊天的信息推送给其它在线的客户端, 该方法会将 channelGroup 中所有的channel 遍历,并发送消息,
            channelGroup.writeAndFlush("[客户端]" + channel.remoteAddress() + " 加入聊天" + sdf.format(new Date()) + " 
    ");
            channelGroup.add(channel);
        }
    
        /**
         * channel 关闭事件
         */
        @Override
        public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
            Channel channel = ctx.channel();
            channelGroup.writeAndFlush("[客户端]" + channel.remoteAddress() + " 离开了
    ");
            System.out.println("channelGroup size" + channelGroup.size());
        }
    
        /**
         * channel 活跃
         */
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println(ctx.channel().remoteAddress() + " 上线了~");
        }
    
        /**
         * channel 不活跃
         */
        @Override
        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
            System.out.println(ctx.channel().remoteAddress() + " 离线了~");
        }
    
        /**
         * 读取消息
         */
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
            Channel channel = ctx.channel();
            //这时我们遍历channelGroup, 根据不同的情况,回送不同的消息
            channelGroup.forEach(ch -> {
                if (channel != ch) { //不是当前的channel,转发消息
                    ch.writeAndFlush("[客户]" + channel.remoteAddress() + " 发送了消息" + msg + "
    ");
                } else { // 回显自己发送的消息给自己
                    ch.writeAndFlush("[自己]发送了消息" + msg + "
    ");
                }
            });
        }
    
        /**
         * 异常发生
         */
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            //关闭通道
            ctx.close();
        }
    }

    3. ChatClient

    package netty.chat;
    
    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.string.StringDecoder;
    import io.netty.handler.codec.string.StringEncoder;
    
    import java.util.Scanner;
    
    public class ChatClient {
    
        private final String host;
        private final int port;
    
        public ChatClient(String host, int port) {
            this.host = host;
            this.port = port;
        }
    
        public void run() throws Exception {
            EventLoopGroup group = new NioEventLoopGroup();
            try {
                Bootstrap bootstrap = new Bootstrap()
                        .group(group)
                        .channel(NioSocketChannel.class)
                        .handler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel ch) throws Exception {
                                //得到pipeline
                                ChannelPipeline pipeline = ch.pipeline();
                                //加入相关handler
                                pipeline.addLast("decoder", new StringDecoder());
                                pipeline.addLast("encoder", new StringEncoder());
                                //加入自定义的handler
                                pipeline.addLast(new ChatClientHandler());
                            }
                        });
    
                ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
                // 得到channel
                Channel channel = channelFuture.channel();
                System.out.println("-------" + channel.localAddress() + "--------");
                // 客户端需要输入信息,创建一个扫描器
                Scanner scanner = new Scanner(System.in);
                while (scanner.hasNextLine()) {
                    String msg = scanner.nextLine();
                    //通过channel 发送到服务器端
                    channel.writeAndFlush(msg + "
    ");
                }
            } finally {
                group.shutdownGracefully();
            }
        }
    
        public static void main(String[] args) throws Exception {
            new ChatClient("127.0.0.1", 8989).run();
        }
    }

    4.  ChatClientHandler

    package netty.chat;
    
    
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.SimpleChannelInboundHandler;
    
    public class ChatClientHandler extends SimpleChannelInboundHandler<String> {
    
        /**
         * 读取消息事件
         */
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
            System.out.println(msg.trim());
        }
    }

    上面代码已经完成了群聊,在群聊的基础上加入心跳检测机制:当服务器超过3秒没有读时,就提示读空闲;当服务器超过5秒没有写操作时,就提示写空闲;实现当服务器超过7秒没有读或者写操作时,就提示读写空闲

    1. 修改服务器端代码加入心跳检测

    package netty.chat;
    
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.channel.*;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.SocketChannel;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    import io.netty.handler.codec.string.StringDecoder;
    import io.netty.handler.codec.string.StringEncoder;
    import io.netty.handler.timeout.IdleStateHandler;
    import netty.heat.MyServerHandler;
    
    import java.util.concurrent.TimeUnit;
    
    public class ChatServer {
    
        public static void main(String[] args) throws InterruptedException {
            EventLoopGroup bossGroup = new NioEventLoopGroup(1);
            EventLoopGroup workerExecutors = new NioEventLoopGroup();
    
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerExecutors)
                    .channel(NioServerSocketChannel.class) // 设置服务器的通道
                    .option(ChannelOption.SO_BACKLOG, 128) // 设置线程队列得到连接个数
                    .childOption(ChannelOption.SO_KEEPALIVE, true) // 设置保持活动连接状态
                    .childHandler(new ChannelInitializer<SocketChannel>() { // 设置通道测试对象(匿名对象)
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            //向pipeline加入解码器
                            pipeline.addLast("decoder", new StringDecoder());
                            //向pipeline加入编码器
                            pipeline.addLast("encoder", new StringEncoder());
                            // 给pipeline添加一个handler
                            socketChannel.pipeline().addLast(new ChatServerHandler());
    
                            // 加入一个netty 提供的 IdleStateHandler
                            /*
                                说明
                                1. IdleStateHandler 是netty 提供的处理空闲状态的处理器
                                2. long readerIdleTime : 表示多长时间没有读, 就会发送一个心跳检测包检测是否连接
                                3. long writerIdleTime : 表示多长时间没有写, 就会发送一个心跳检测包检测是否连接
                                4. long allIdleTime : 表示多长时间没有读写, 就会发送一个心跳检测包检测是否连接
                                5. 文档说明
                                triggers an {@link IdleStateEvent} when a {@link Channel} has not performed read, write, or both operation for a while.
                                6. 当 IdleStateEvent 触发后 , 就会传递给管道 的下一个handler去处理
                                通过调用(触发)下一个handler 的 userEventTiggered , 在该方法中去处理 IdleStateEvent(读空闲,写空闲,读写空闲)
                             */
                            pipeline.addLast(new IdleStateHandler(7000, 7000, 10, TimeUnit.SECONDS));
                            //加入一个对空闲检测进一步处理的handler(自定义)
                            pipeline.addLast(new MyServerHandler());
                        }
                    });
    
            ChannelFuture sync = serverBootstrap.bind(8989).sync();
            sync.addListener(future -> {
                if (future.isSuccess()) {
                    System.out.println("服务器绑定8989端口成功");
                }
            });
        }
    }

    2. MyServerHandler 心跳检测

    package netty.heat;
    
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.ChannelInboundHandlerAdapter;
    import io.netty.handler.timeout.IdleStateEvent;
    
    public class MyServerHandler extends ChannelInboundHandlerAdapter {
    
        public MyServerHandler() {
            System.out.println("netty.heat.MyServerHandler.MyServerHandler");
        }
    
        @Override
        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
            if (evt instanceof IdleStateEvent) {
                //将  evt 向下转型 IdleStateEvent
                IdleStateEvent event = (IdleStateEvent) evt;
                String eventType = null;
                switch (event.state()) {
                    case READER_IDLE:
                        eventType = "读空闲";
                        break;
                    case WRITER_IDLE:
                        eventType = "写空闲";
                        break;
                    case ALL_IDLE:
                        eventType = "读写空闲";
                        break;
                }
    
                System.out.println(ctx.channel().remoteAddress() + "--超时时间--" + eventType);
                System.out.println("服务器做相应处理..");
                //如果发生空闲,我们关闭通道
                // ctx.channel().close();
            }
        }
    }
    【当你用心写完每一篇博客之后,你会发现它比你用代码实现功能更有成就感!】
  • 相关阅读:
    CodeForces
    EOJ 3506. 斐波那契数列
    牛客练习赛13 D幸运数字Ⅳ . 康托逆展开
    UVA
    Piggy-Bank HDU
    Dollar Dayz POJ
    UVA 674 Coin Change (完全背包)
    python OOP (1)
    python lambda简易使用
    python whl模块安装方法
  • 原文地址:https://www.cnblogs.com/qlqwjy/p/14601705.html
Copyright © 2020-2023  润新知