Netty实战练习——聊天室

目录

 

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

环境要求:

项目结构:

代码详情:

1、ChatServerHandler.java

SimpleChannelInboundHandler:

ChannelGroup:

2、ChatServer.java

DelimiterBasedFrameDecoder:

3、ChatClientHandler.java

4、ChatClient.java

运行效果:

源码地址:


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

  • 当client连接或者掉线信息会显示在server 控制台中,并且server可以转发给其他client
  • 某client发的消息,server可以转发给其他各个client

环境要求:

  • JDK 1.8
  • Maven 3.3.3
  • Netty 4.1

项目结构:

Netty实战练习——聊天室_第1张图片

代码详情:

1、ChatServerHandler.java

public class ChatServerHandler extends SimpleChannelInboundHandler {
    //Netty提供了ChannelGroup接口,该接口继承Set接口
    // 因此可以通过ChannelGroup可管理服务器端所有的连接的Channel,然后对所有的连接Channel广播消息。
    public static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    /**
     * 每当服务端收到新的客户端连接时,客户端的channel存入ChannelGroup列表中,并通知列表中其他客户端channel
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        //获取连接的channel
        Channel incomming = ctx.channel();
        //通知所有已经连接到服务器的客户端,有一个新的通道加入
        for (Channel channel : channelGroup) {
            channel.writeAndFlush("[系统消息]-" + incomming.remoteAddress() + "加入\n");
        }
        channelGroup.add(ctx.channel());
    }

    /**
     * 每当服务端断开客户端连接时,客户端的channel从ChannelGroup中移除,并通知列表中其他客户端channel
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        //获取连接的channel
        Channel incomming = ctx.channel();
        for (Channel channel : channelGroup) {
            channel.writeAndFlush("[系统消息]-" + incomming.remoteAddress() + "离开\n");
        }
        //从服务端的channelGroup中移除当前离开的客户端
        channelGroup.remove(ctx.channel());
    }

    /**
     * 服务端监听到客户端活动
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //服务端接收到客户端上线通知
        Channel incoming = ctx.channel();
        System.out.println("ChatClient:" + incoming.remoteAddress() + "在线");
    }

    /**
     * 服务端监听到客户端不活动
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        //服务端接收到客户端掉线通知
        Channel incoming = ctx.channel();
        System.out.println("ChatClient:" + incoming.remoteAddress() + "掉线");
    }

    /**
     * 每当从服务端读到客户端写入信息时,将信息转发给其他客户端的Channel.
     *
     * @param channelHandlerContext
     * @param o
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
        Channel incomming = channelHandlerContext.channel();
        //将收到的信息转发给全部的客户端channel
        for (Channel channel : channelGroup) {
            if (channel != incomming) {
                channel.writeAndFlush("[" + incomming.remoteAddress() + "]" + o.toString() + "\n");
            } else {
                channel.writeAndFlush("[You]" + o.toString() + "\n");
            }
        }
    }

}

SimpleChannelInboundHandler:

入站的Handler可能会继承SimpleChannelInboundHandler或者ChannelInboundHandlerAdapter,而SimpleChannelInboundHandler又是继承于ChannelInboundHandlerAdapter,最大的区别在于SimpleChannelInboundHandler会对没有外界引用的资源进行一定的清理,并且入站的消息可以通过泛型来规定。

public abstract class SimpleChannelInboundHandler extends ChannelInboundHandlerAdapter


ChannelGroup:


Netty提供了ChannelGroup接口,该接口继承Set接口,因此可以通过ChannelGroup可管理服务器端所有的连接的Channel,然后对所有的连接Channel广播消息。

2、ChatServer.java

public class ChatServer {
    int port;

    public ChatServer(int port) {
        this.port = port;
    }

    public void run() throws Exception{
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline()
                                    //DelimiterBasedFrameDecoder用来解决以特殊符号作为消息结束符的粘包问题
                                    .addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()))
                                    .addLast("decoder", new StringDecoder())
                                    .addLast("encoder", new StringEncoder())
                                    .addLast(new ChatServerHandler());
                        }
                    })
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            System.out.println("ChatServer 启动了");
            ChannelFuture f = serverBootstrap.bind(port).sync();

            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
            System.out.println("ChatServer 关闭了");
        }
    }

    public static void main(String[] args) throws Exception {
        int port;
        if (args.length > 0) {
            port = Integer.parseInt(args[0]);
        } else {
            port = 8080;
        }
        new ChatServer(port).run();
    }
}

DelimiterBasedFrameDecoder:

用来解决以特殊符号作为消息结束符的粘包问题,FixedLengthFrameDecoder用来解决定长消息的粘包问题。

3、ChatClientHandler.java

public class ChatClientHandler extends SimpleChannelInboundHandler {
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
        System.out.println(o.toString());
    }
}

4、ChatClient.java

public class ChatClient  {
    int port;
    String host;

    public ChatClient(int port, String host) {
        this.port = port;
        this.host = host;
    }

    public void run() {
        EventLoopGroup group = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        try {
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline()
                                    .addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()))
                                    .addLast("decoder", new StringDecoder())
                                    .addLast("encoder", new StringEncoder())
                                    .addLast(new ChatClientHandler());

                        }
                    });

            Channel channel = bootstrap.connect(host, port).sync().channel();

            BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
            while (true) {
                channel.writeAndFlush(in.readLine() + "\r\n");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        new ChatClient(8080, "localhost").run();
    }

运行效果:

Netty实战练习——聊天室_第2张图片

Netty实战练习——聊天室_第3张图片

Netty实战练习——聊天室_第4张图片

源码地址:

https://github.com/tianyaning/code.git

你可能感兴趣的:(netty)