Netty之实现一个简单的群聊系统

要求

  1. 编写一个 Netty 群聊系统,实现服务器端和客户端之间的数据简单通讯(非阻塞)

  2. 实现多人群聊

  3. 服务器端:可以监测用户上线,离线,并实现消息转发功能

  4. 客户端:通过channel可以无阻塞发送消息给其它所有用户,同时可以接受其它用户发送的消息(由服务器转发得到)

代码演示

服务端

public class GroupChatServer {
​
    private int port;
​
    public GroupChatServer(int port) {
        this.port = port;
    }
​
    /**
     * 编写run方法,处理客户端的请求
     *
     * @throws Exception
     */
    public void run() throws Exception {
​
        //创建两个线程组
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
​
        try {
            ServerBootstrap b = new ServerBootstrap();
​
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .childHandler(new ChannelInitializer() {
​
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
​
                            //获取到pipeline
                            ChannelPipeline pipeline = ch.pipeline();
                            //向pipeline加入解码器
                            pipeline.addLast("decoder", new StringDecoder());
                            //向pipeline加入编码器
                            pipeline.addLast("encoder", new StringEncoder());
                            //加入自己的业务处理handler
                            pipeline.addLast(new GroupChatServerHandler());
​
                        }
                    });
​
            System.out.println("netty 服务器启动");
            ChannelFuture channelFuture = b.bind(port).sync();
​
            //监听关闭
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
​
    }
​
    public static void main(String[] args) throws Exception {
​
        new GroupChatServer(7000).run();
    }
}

 

服务端业务处理类

public class GroupChatServerHandler extends SimpleChannelInboundHandler {
    //使用一个hashMap 管理
    //public static Map channels = new HashMap();
​
    /**
     * 定义一个channel 组,管理所有的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
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        //将该客户加入聊天的信息推送给其它在线的客户端
        //该方法会将 channelGroup 中所有的channel 遍历,并发送消息,我们不需要自己遍历
        channelGroup.writeAndFlush("[客户端]" + channel.remoteAddress() + " 加入聊天" + sdf.format(new java.util.Date()) + " \n");
        channelGroup.add(channel);
    }
​
    /**
     * 断开连接, 将xx客户离开信息推送给当前在线的客户
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
​
        Channel channel = ctx.channel();
        channelGroup.writeAndFlush("[客户端]" + channel.remoteAddress() + " 离开了\n");
        System.out.println("channelGroup size" + channelGroup.size());
​
    }
​
    /**
     * 表示channel 处于活动状态, 提示 xx上线
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
​
        System.out.println(ctx.channel().remoteAddress() + " 上线了~");
    }
​
    /**
     * 表示channel 处于不活动状态, 提示 xx离线了
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
​
        System.out.println(ctx.channel().remoteAddress() + " 离线了~");
    }
​
    /**
     * 读取数据
     *
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        //获取到当前channel
        Channel channel = ctx.channel();
        //这时我们遍历channelGroup, 根据不同的情况,回送不同的消息
​
        channelGroup.forEach(ch -> {
            if (channel != ch) {
                //不是当前的channel,转发消息
                ch.writeAndFlush("[客户]" + channel.remoteAddress() + " 发送了消息" + msg + "\n");
            } else {
                //回显自己发送的消息给自己
                ch.writeAndFlush("[自己]发送了消息" + msg + "\n");
            }
        });
    }
​
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        //关闭通道
        ctx.close();
    }
}

客户端

public class GroupChatClient {
    private final String host;
    private final int port;
​
    public GroupChatClient(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() {
​
                        @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 GroupChatClientHandler());
                        }
                    });
​
            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 + "\r\n");
            }
        } finally {
            group.shutdownGracefully();
        }
    }
​
    public static void main(String[] args) throws Exception {
        new GroupChatClient("127.0.0.1", 7000).run();
    }
}

客户端业务处理类

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

演示效果

启动1个server端,开启3个client端,互相聊天,记录如下:

server

netty 服务器启动
/127.0.0.1:55072 上线了~
/127.0.0.1:55076 上线了~
/127.0.0.1:55080 上线了~
/127.0.0.1:55072 离线了~
channelGroup size2
/127.0.0.1:55076 离线了~
channelGroup size1
/127.0.0.1:55080 离线了~
channelGroup size0

client1

-------/127.0.0.1:55072--------
[客户端]/127.0.0.1:55076 加入聊天2019-12-13 18:22:43
[客户端]/127.0.0.1:55080 加入聊天2019-12-13 18:22:46
大家好
[自己]发送了消息大家好
[客户]/127.0.0.1:55076 发送了消息你吃了吗
我要走了,88
[自己]发送了消息我要走了,88
​
Process finished with exit code 130 (interrupted by signal 2: SIGINT)
​

client2

-------/127.0.0.1:55076--------
[客户端]/127.0.0.1:55080 加入聊天2019-12-13 18:22:46
[客户]/127.0.0.1:55072 发送了消息大家好
你吃了吗
[自己]发送了消息你吃了吗
[客户]/127.0.0.1:55072 发送了消息我要走了,88
[客户端]/127.0.0.1:55072 离开了
我也要走了
[自己]发送了消息我也要走了
​
Process finished with exit code 130 (interrupted by signal 2: SIGINT)

client3

-------/127.0.0.1:55080--------
[客户]/127.0.0.1:55072 发送了消息大家好
[客户]/127.0.0.1:55076 发送了消息你吃了吗
[客户]/127.0.0.1:55072 发送了消息我要走了,88
[客户端]/127.0.0.1:55072 离开了
[客户]/127.0.0.1:55076 发送了消息我也要走了
[客户端]/127.0.0.1:55076 离开了
没人了,没意思啊,我也走了
[自己]发送了消息没人了,没意思啊,我也走了
​
Process finished with exit code 130 (interrupted by signal 2: SIGINT)

你可能感兴趣的:(netty)