从Netty入门案例中看懂Netty工作模型

那年仲夏 提交于 2020-08-08 05:12:51

Netty工作模型

最近在学习Netty,遂记录下一些学习心得。文章中的内容是在学习了李林峰老师的《netty权威指南》以及尚硅谷的Netty视频后的一些感想以及对netty内容的学习记录。

单Reactor多线程模型中,只有一个selector,负责accept,read,write事件的维护
Netty主要基于 主从Reactors多线程模型,顾名思义,使用一个主Selector,和一个从Selector
主Selector 只负责客户端的连接,即accept事件。
从Selector 负责客户端的读写。即read和write事件。


代码示例

以服务端代码为例,相关的解释都写在了注释里

//Server 代码
public class EchoServer {

    public void bind(int port) throws  Exception{
        /**
         * bossGroup 和workerGroup包含的子线程(NioEventLoop)的个数
         * 默认实际 cpu核数 * 2
         */

        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup();

        try{
            /**
             * step1.
             * 创建server端的启动对象
             * 这里之所以使用无参构造方法是因为,创建该对象所需要的参数太多了,未来也有可能发生变化
             * 所以引用了Builder模式
             */
            ServerBootstrap b = new ServerBootstrap();
            //为其配置相关参数
            /**
             * step2.
             * 从group 方法中可以得知我们传入了bossGroup和workGroup
             * 从bossGroup和workGroup对象创建时的类可以知道他们都是一个NioEventLoopGroup
             * 即我们 之前说的 主从Reactor多线程模型
             * group 方法就是设置 并绑定 Reactor线程池
             * 每个NioEventLoopGroup 中的实例是一个 NioEventLoop对象
             * NioEventLoop对象 的职责就是处理所有注册到本线程的多路复用器Selector上的Channel,
             * 通俗的讲就是NioEventLoop 是 NioEventLoopGroup中的一员,NioEventLoopGroup可以有多个NioEventLoop
             * 而每个NioEventLoop 都对应一个Selector,不断轮询监听绑定在Selector上面的事件(这里需要NIO的知识)
             */
            b.group(bossGroup, workGroup);
            /**
             * step3.
             * channel()方法用于设置并绑定服务端的Channel
             */
            b.channel(NioServerSocketChannel.class);
            b.option(ChannelOption.SO_BACKLOG, 1024);
            /**
             * step4.
             * 设置父类的channelHandle
             */
            b.handler(new LoggingHandler(LogLevel.INFO));
            /**
             * step4.
             * 设置子类的Handler.并为Channel中的pipeline 增加handler
             */
            b.childHandler(new ChannelInitializer<SocketChannel>() {
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ByteBuf delimiter = Unpooled.copiedBuffer("$_".getBytes());
                            socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, delimiter));
                            socketChannel.pipeline().addLast(new StringDecoder());
                            socketChannel.pipeline().addLast(new EchoServerHandler());
                        }
                    });
            /**
             * 这里为什么要对父类和子类分别设置handler,以及子类的pipeline 设置handler
             * 通过group()方法的源码,我得到了如下的结论
             * group()的源码中 bossGroup是设置到其父类方法中,而workerGroup则被设置到了childGroup中
             * handler() 的源码,其传入的handler 对象也是被赋值到了父类的handler中
             * 再结合主从Reactor 多线程模型,主Reactor 只负责连接,从Reactor 只负责read/write
             * 说明父类的handler 是用于处理连接的,
             * childHandler 是用于处理read/write 的,所以为其对应的NioEventLoop 所在的channel 中的pipeline增加了相关业务类的handler
             *
             */

            ChannelFuture f = b.bind(port).sync();
            f.channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }


    private class  ChildChannelHandler extends ChannelInitializer<SocketChannel> {

        protected void initChannel(SocketChannel socketChannel) throws Exception {
            /**
             * 为解决TCP 粘包问题
             * 增加半包解码器和字符串解码器
             */
            //增加半包解码器
            socketChannel.pipeline().addLast(new LineBasedFrameDecoder(1024));
            //增加字符串解码器
            socketChannel.pipeline().addLast(new StringDecoder());

            socketChannel.pipeline().addLast(new TimeServerHandler());
        }
    }


    public static void main(String[] args) throws Exception {

        int port = 8080;
        new EchoServer().bind(port);
    }
}
//SERVER handler代码
public class EchoServerHandler extends ChannelHandlerAdapter {

    private int counter;

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        String body = (String)msg;

        System.out.println("the counter is :"+ ++counter + "times receive client : [" + body +"]");
        body += "$_";
        ByteBuf echo = Unpooled.copiedBuffer(body.getBytes());
        ctx.writeAndFlush(echo);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}
//Client端代码
public class EchoClient {

    public  void connect(String host, int port) throws Exception{
        EventLoopGroup group = new NioEventLoopGroup();
        try{
            Bootstrap b = new Bootstrap();
            b.group(group)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer<SocketChannel>() {

                        @Override
                        public void initChannel(SocketChannel socketChannel) throws Exception {
                            ByteBuf delimiter = Unpooled.copiedBuffer("$_".getBytes());
                            socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, delimiter));
                            socketChannel.pipeline().addLast(new StringDecoder());
                            socketChannel.pipeline().addLast(new EchoClientHandler());
                        }
                    });


            ChannelFuture f = b.connect(host,port).sync();
            f.channel().closeFuture().sync();
        }finally {
            group.shutdownGracefully();
        }
    }


    public static void main(String[] args) throws Exception {
        new EchoClient().connect("127.0.0.1",8080);
    }
}
//clientHandler
public class EchoClientHandler extends ChannelHandlerAdapter {
    private int counter;
    static final String ECHO_REQ = "hello, I am donkeyx.$_";


    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //super.channelActive(ctx);
        ByteBuf message = null;
        for (int i=0; i< 10; i++){
            message = Unpooled.copiedBuffer(ECHO_REQ.getBytes());
            ctx.writeAndFlush(message);
        }
    }


    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        String body = (String)msg;
        System.out.println("the counter is :"+ ++counter + "times receive client : [" + body +"]");
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception{
        ctx.flush();
    }


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

结论

通过这个echo demo可以得出

  1. BossGroup (NioEventLoopGroup) : 只负责客户端的连接
  2. WorkerGroup (NioEventLoopGroup) : 负责网络的读写
  3. NioEventLoopGroup : 相当于一个事件循环组,组中包含多个事件循环。每个事件循环是一个 NioEventLoop
  4. NioEventLoop 表示一个不断循环的执行处理任务的线程,每个NioEventLoop 有一个Selector,用于监听绑定在其上的socket 的网络通讯
  5. NioEventLoopGroup 可以有多个线程,即可以含有多个NioEventLoop
  6. 每个Boss 的NioEventLoop 执行的步骤有3布
    a. 轮询accept事件,
    b. 处理accept事件,与client建立连接,生成NioSocketChannel,并将其注册到某个worker de NioEventLoop 的selector
    C. 处理任务队列的任务


  7. 每个Worker的NioEventLoop循环执行的步骤
    a. 轮询read,write事件
    b. 处理I/O(read,write)事件。在对应的NioSocketChannel上进行处理
    C. 处理任务队列的任务


  8. worker NioEventLoop 在处理业务时,会使用Pipeline.pipeline中包含了channel,即通过pipeline 可与i获取到对应的通道。管道中维护了许多的handler,用于处理自定义的任务

该工作模型的图例

Netty线程模型.png

父类Handler和childHandler 的区别

父类Handler和childHandler区别.png

本质区别就是:

父类中的Handler,每个新连接都会经过他,然后他为每个新连接创建新的Handler。
childHandler 就只负责read/write。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!