netty实现一个简单的聊天室

使用netty实现一个简单的聊天室

需求

当有用户连接时广播消息=>XXX加入聊天室,用户断开连接时广播消息=>XXX离开聊天室,并且用户发送消息时,服务端进行广播

Server端实现

Server代码:

public class ChatServer {
    public static void main(String[] args) {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        ServerBootstrap cb = new ServerBootstrap();
        try {
            //这里直接用匿名内部类构造channel初始化类
            cb.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
                protected void initChannel(SocketChannel socketChannel) throws Exception {
                    ChannelPipeline pipeline = socketChannel.pipeline();
                    pipeline.addLast(new DelimiterBasedFrameDecoder(4096, Delimiters.lineDelimiter()))//分割器,分割每行数据
                            .addLast(new StringDecoder(CharsetUtil.UTF_8))
                            .addLast(new StringEncoder(CharsetUtil.UTF_8))
                            .addLast(new ChatServerHandler());
                }
            });
            ChannelFuture channelFuture = cb.bind( 9999).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

handler代码:

public class ChatServerHandler extends SimpleChannelInboundHandler<String> {
    //这里是一个channel容器,用于管理所有没有断开的连接,广播消息
    static ChannelGroup cg = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        Channel me = ctx.channel();
        System.out.println("来自=>"+me.remoteAddress()+"的消息:"+msg+"\n");
        cg.forEach(ch->{
            if(me != ch){
                ch.writeAndFlush("来自=>"+me.remoteAddress()+"的消息:"+msg+"\n");
            }
            else {
                ch.writeAndFlush("自己:"+msg+"\n");
            }
        });
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        cg.writeAndFlush("[系统提示]-用户:"+channel.remoteAddress()+"-加入聊天室\n");
        cg.add(channel);
        System.out.println(channel.remoteAddress()+" 上线,当前容量:"+cg.size());
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        cg.writeAndFlush("[系统提示]-用户:"+channel.remoteAddress()+"-离开聊天室\n");
        //cg.remove(channel); ChannelGroup 容器会自动清除断开的连接,所以不需要手动断开
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        cg.writeAndFlush("[系统提示]-用户:"+channel.remoteAddress()+"-上线\n");
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        cg.writeAndFlush("[系统提示]-用户:"+channel.remoteAddress()+"-下线\n");
    }
}

上述所有发送的消息最后都加了一个换行符,是因为我们下面配置客户端channel的时候也要要求数据带换行分来进行分割,若不换行它会认为这条消息没有处理完,不会放行到后面执行

客户端实现

Server代码:

public class ClientServer {
    public static void main(String[] args) {
        EventLoopGroup group = new NioEventLoopGroup();
        Bootstrap cb = new Bootstrap();
        try {
            //这里直接用匿名内部类构造channel初始化类
            cb.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {
                protected void initChannel(SocketChannel socketChannel) throws Exception {
                    ChannelPipeline pipeline = socketChannel.pipeline();
                    pipeline.addLast(new DelimiterBasedFrameDecoder(4096, Delimiters.lineDelimiter()))
                            .addLast(new StringDecoder(CharsetUtil.UTF_8))
                            .addLast(new StringEncoder(CharsetUtil.UTF_8))
                            .addLast(new ChatClientHandler());
                }
            });

            Channel channel = cb.connect("localhost", 9999).sync().channel();
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            while (true) {
                channel.writeAndFlush(br.readLine()+"\r\n");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            group.shutdownGracefully();
        }
    }
}

handler代码:

public class ChatClientHandler  extends SimpleChannelInboundHandler<String> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println(msg);
    }
}

由于客户端接收消息只需要显示就行了,所以handler很简单

运行

我们首先启动服务端,然后依次开三个客户端
第一个客户端连接时:
服务端
netty实现一个简单的聊天室

客户端1
netty实现一个简单的聊天室

第二个客户端连接时
服务端
netty实现一个简单的聊天室

客户端1
netty实现一个简单的聊天室

客户端2
netty实现一个简单的聊天室

第三个客户端连接时
服务端
netty实现一个简单的聊天室

客户端1
netty实现一个简单的聊天室

客户端2
netty实现一个简单的聊天室

客户端3
netty实现一个简单的聊天室

客户端3发送消息时
客户端1
netty实现一个简单的聊天室

客户端2
netty实现一个简单的聊天室

客户端3
netty实现一个简单的聊天室

客户3断开时
客户端1
netty实现一个简单的聊天室

客户端2
netty实现一个简单的聊天室

总结

这是个很经典的程序,这里也主要用了一个DelimiterBasedFrameDecoder 的handler初始化channel的管道,使其分割换行发送消息

netty实现一个简单的聊天室

上一篇:Lucene Wikipedia


下一篇:如何用VSCode+phpStudy配置PHP开发环境