• Netty章节四:实现多人聊天功能/多客户端连接并且互相通信


    具体功能:

    1.服务器启动,n多个客户端与服务器进行连接,一个客户端上线之后,服务器端控制台会打印xx上线了,其他的客户端控制台打印xx上线了。如果一个客户端下线了,服务器端的控制台上打印,xx下线了,其他的客户端控制台打印xx下线了。

    2.多个客户端都上线之后,一个客户端(比如说A)给服务端发送消息,那么客户端(比如说A,B,C,包括自己本身)都会收到消息,对于A来说,会标志此消息是自己发送自己的,其他的客户端则会收到具体的消息。

    服务器代码

    服务端主启动类

    public class MyChatServer {
        public static void main(String[] args){
            EventLoopGroup bossGroup = new NioEventLoopGroup();
            EventLoopGroup workerGroup = new NioEventLoopGroup();
            try {
                ServerBootstrap serverBootstrap = new ServerBootstrap();
                serverBootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class)
                        .childHandler(new MyChatServerInitializer());
    
                ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
                channelFuture.channel().closeFuture().sync();
            }catch (Exception e){
                System.out.println("服务端异常");
                System.out.println(e.getMessage());
            }finally {
                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
            }
        }
    }
    

    初始化器 (Initializer)

    public class MyChatServerInitializer extends ChannelInitializer<SocketChannel> {
        @Override
        protected void initChannel(SocketChannel ch) throws Exception {
            ChannelPipeline pipeline = ch.pipeline();
            /*
                DelimiterBasedFrameDecoder 解码器  根据分隔符进行解析
                StringDecoder 字符串解码器
                StringEncoder 字符串编码器
             */
            pipeline.addLast(new DelimiterBasedFrameDecoder(4096, Delimiters.lineDelimiter()));
            //虽然默认StringDecoder/StringEncoder默认就是UTF_8,但是最好还是写上
            pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
            pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
            pipeline.addLast(new MyChatServerHandler());
        }
    }
    

    自定义处理器 (Handler)

    //聊天室简单通道  这里的泛型是String,说明这个传输的是个String对象
    public class MyChatServerHandler extends SimpleChannelInboundHandler<String> {
    
        /**
         * 定义一个channelGroup,用来保存多个通道/用户
         * 一个用户表示一个channel,将他们加入到一个组
         */
        private static ChannelGroup channelGroup =
                new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
            Channel channel = ctx.channel();
            channelGroup.forEach(ch -> {
                if(channel != ch){
                    ch.writeAndFlush(channel.remoteAddress() + " 发送的消息:" + msg + "
    ");
                }else {
                    ch.writeAndFlush("【自己】" + msg + "
    ");
                }
            });
    
        }
    
        /**表示连接建立,一旦连接,第一个执行*/
        @Override
        public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
            Channel channel = ctx.channel();
            /**
             * channelGroup的writeAndFlush有点特别,他将循环对里面每一个channel进行输出
             * 如:假如A上线,会通知channelGroup其他channel,但是不会通知A,因为此时没有加入A
             * 如果也想通知自己,那么在输出前将自己加入channelGroup就好(注意他们的顺序)
             *
             */
            channelGroup.writeAndFlush("【服务器】 - " + channel.remoteAddress() + " 加入
    ");
            channelGroup.add(channel);
        }
    
        /**断开连接时执行*/
        @Override
        public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
            Channel channel = ctx.channel();
            /**
             * 如果是离开,相对应的应该移除channel,但是这里不需要
             * 因为netty,自动将它移除了
             */
            channelGroup.writeAndFlush("【服务器】 - " + channel.remoteAddress() + " 离开
    ");
        }
    
        /**表示连接处于活动状态*/
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println(ctx.channel().remoteAddress() + "  上线");
        }
    
        /**表示连接处于不活动状态*/
        @Override
        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
            System.out.println(ctx.channel().remoteAddress() + "  下线");
        }
    
        /**异常的捕获,一般出现异常,就把连接关闭*/
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.close();
        }
    
    }
    

    客户端代码

    客户端主启动类

    public class MyChatClient {
        public static void main(String[] args) {
    
            //事件循环组,只有一个循环组
            EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
            try {
                Bootstrap bootstrap = new Bootstrap();
                bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
                        .handler(new MyChatClientInitializer());
                //与对应的url建立连接通道  .channel 拿到对应的通道对象
                //可以直接与连接该通道的服务交互
                Channel channel = bootstrap.connect("localhost", 8899).sync().channel();
    
                BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
                for (; ;){
                    //readLine每次读取一行
                    //读取一行数据,回车即读取
                    channel.writeAndFlush(br.readLine() + "
    ");
                }
    
            }catch (Exception e){
                System.out.println("异常");
                System.out.println(e.getMessage());
            }finally {
                eventLoopGroup.shutdownGracefully();
            }
        }
    }
    

    初始化器 (Initializer)

    public class MyChatClientInitializer extends ChannelInitializer<SocketChannel> {
        @Override
        protected void initChannel(SocketChannel ch) throws Exception {
            ChannelPipeline pipeline = ch.pipeline();
    
            pipeline.addLast(new DelimiterBasedFrameDecoder(4096, Delimiters.lineDelimiter()));
            //虽然默认StringEncoder默认就是UTF_8,但是最好还是写上
            pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
            pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
            pipeline.addLast(new MyChatClientHandler());
        }
    }
    

    自定义处理器 (Handler)

    public class MyChatClientHandler extends SimpleChannelInboundHandler<String> {
    
        /**
         * @param ctx 上下文请求对象
         * @param msg 表示服务端发来的消息
         * @throws Exception
         */
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
            System.out.println(msg);
        }
    }
    

    测试

    启动服务器,启动第一个客户端的时候,服务器端控制台打印

    /127.0.0.1:47024  上线
    

    第二个客户端启动,则服务器控制台打印

    /127.0.0.1:47024  上线
    /127.0.0.1:47070  上线
    

    此时第一个客户端打印,

    【服务器】 -/127.0.0.1:47070 加入
    

    启动第三个客户端依次可以自己验证,当我在客户端1控制台输入信息的时候,客户端1控制台打印了【自己】来自第一个客户端的问候

    来自第一个客户端的问候
     【自己】来自第一个客户端的问候
    

    其他的二个客户端控制台打印

    /127.0.0.1:47024 发送的消息:来自第一个客户端的问候
    

    如果其他的客户端下线之后,比如客户端1下线,服务器端控制台打印

    /127.0.0.1:47024 下线
    

    其他客户端控制台打印:

    【服务器】 -/127.0.0.1:47024 离开
    

    使用lsof命令查看端口映射关系

    请输入图片描述

  • 相关阅读:
    C# 文件类的操作---删除
    C#实现Zip压缩解压实例
    UVALIVE 2431 Binary Stirling Numbers
    UVA 10570 meeting with aliens
    UVA 306 Cipher
    UVA 10994 Simple Addition
    UVA 696 How Many Knights
    UVA 10205 Stack 'em Up
    UVA 11125 Arrange Some Marbles
    UVA 10912 Simple Minded Hashing
  • 原文地址:https://www.cnblogs.com/mikisakura/p/12983512.html
Copyright © 2020-2023  润新知