Netty源码分析之接收请求

一、入口

在第二节(Netty简单启动过程)中我们介绍了,netty的简单启动,我们知道端口绑定完成之后,就开始等待来自于客户端的请求,以及处理客户端的请求等一系列操作,现在让我们看看netty是怎么操作的?
当我们回顾第二节中channel注册过程源码时可以发现有下边这段代码。

    public final void register(EventLoop eventLoop, final ChannelPromise promise) {
            AbstractChannel.this.eventLoop = eventLoop;//这里是把当前的EventLoop赋值给
            //当前channel的eventLoop属性
             eventLoop.execute(new Runnable() {
                    @Override
                     public void run() {
                         register0(promise);
                     }
               });
            }
    }

从代码可以看出,channel的注册是通过EventLoop(当前的EventLoop是NioEventLoop的一个实例,具体这个实例是怎么分配的,后续再单独说明,可以知道的一点是当前的EventLoop是通过group来分配的)来实现的,execute方法主要就是往任务队列中添加任务,然后启动线程(如果当前线程没有启动)。回到doBind0方法

    private static void doBind0(
            final ChannelFuture regFuture, final Channel channel,
            final SocketAddress localAddress, final ChannelPromise promise) {
        //这里的eventLoop就是上边注册方法赋值的
        channel.eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                    channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); 
            }
        });
    }

具体的端口绑定也是通过EventLoop来完成,此时继续往任务队列中添加一个端口绑定的任务,线程在注册的时候已经启动了,到这里我们就已经可以大致猜出,接收客户端的请求的入口在哪了。
注册和绑定都是通过EventLoop来完成,说明接收客户端的请求也是在这里完成(在注册的时候,我们知道开启了线程),所以我们先看看EventLoop中的run方法是怎么执行的。

    protected void run() {
        int selectCnt = 0;
        for (;;) {
        	//省略了绝大部分代码
              select(curDeadlineNanos);              
              processSelectedKeys();
          }            
    }

这里提取整个run方法的关键两行代码,第一行就是我们很熟悉的代码select(curDeadlineNanos);也就是调用底层selector的select方法的入口,第二行就是处理具体请求的方法入口processSelectedKeys(),如果我们degug代码的时候,发现最终都会调用processSelectedKey(k, (AbstractNioChannel) a)这个方法。

    private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
        final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
        try {
           if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
                unsafe.read();//客户端的accept和读都是走这里
            }
 
    }

我们看看read方法,从方法来看主要就是读取信息,然后处理信息。

        public void read() {
                doReadMessages(readBuf);
                pipeline.fireChannelRead(readBuf.get(i));
 
        }

查看doReadMessages方法源码可以知道,当前方法是往readBuf集合中添加NioSocketChannel对象(buf.add(new NioSocketChannel(this, ch)),其中ch就是底层的SocketChannel)。pipeline.fireChannelRead(readBuf.get(i))这个方法就是处理当前集合中的所有元素,这里可以看出是通过pipeline来获取一个handler执行,我们在第三节的时候也简单介绍了这个pipeline,回顾第二节中的初始化方法调用init:

    void init(Channel channel) {
        ChannelPipeline p = channel.pipeline();
        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) {
                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });
    }

源码可以知道,我们在初始化的时候已经向NioServerSocketChannel的pipeline属性中添加了一个ChannelInitializer的handler,其中其initChannel需要我们自己实现,这个initChannel方法在channelRegistered这个方法和handlerAdded方法中调用,对于NioServerSocketChannel来说,第一次会在channelRegistered方法中调用,我们知道channel注册完成之后就会调用pipeline.fireChannelRegistered()方法。所以当注册结束之后,就会调用上边ChannelInitializer的initChannel方法,把ServerBootstrapAcceptor添加到pipeline中。所以当read方法中调用fireChannelRead方法时,就会直接调用到ServerBootstrapAcceptor中的channelRead方法

        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            final Channel child = (Channel) msg;//上边可知这里的channel
            //就是NioSocketChannel
            child.pipeline().addLast(childHandler);//这里的childHandler就是
            //最开始启动的是自定义添加的handler
            setChannelOptions(child, childOptions, logger);//设置子参数
            setAttributes(child, childAttrs);//设置子属性
            try {
                childGroup.register(child).addListener(new ChannelFutureListener() {
                //通过子group注册channel,这里的channel注册就很简单,就是执行handler的register和active方法
                    @Override
                    public void operationComplete(ChannelFuture future) throws Exception {
                        if (!future.isSuccess()) {
                            forceClose(child, future.cause());
                        }
                    }
                });
            } catch (Throwable t) {
                forceClose(child, t);
            }
        }

从这里我们可以知道,真正接收客户端数据,就是通过childGroup来实现的,也就是从childGroup中分配的EventLoop来处理,处理逻辑同上。

二、细节

第一部分只是简单介绍了数据接收和处理的入口,相信大部分人还是比较懵,逻辑混乱的感觉。所以这部分主要是厘清第一部分的逻辑。

  • 回到第一节(初识Netty)的样例,有这样两个group,这两个group非常的关键,从命名来看一个是主group,一个是工作group,这里我们需要记住有这两个group,并且从bootstrap的group方法可以知道,boss赋值给了AbstractBootstrap中的group属性,work赋值给了ServerBootstrap中的childGroup属性。
      EventLoopGroup boss=new NioEventLoopGroup();
      EventLoopGroup work=new NioEventLoopGroup();
      ServerBootstrap b=new ServerBootstrap();
      b.group(boss,work)

我们先不用管其功能,先记住上边这个关系。

  • 回顾绑定过程,找出在绑定过程当中用到这两个group的地方
    1、在初始化init(channel)方法中,childGroup作为构造参数传入到了ServerBootstrapAcceptor中
    2、initAndRegister()调用注册的时候config().group().register(channel),先通过配置config得到group,config().group()返回的是group属性也就是上边的boss参数。查看group的register方法,实际调用next().register(channel).
    public ChannelFuture register(Channel channel) {
        return next().register(channel);
    }

next(),方法返回值是一个EventLoop对象(这就是一个具体的线程对象),如果继续跟踪父类的next方法,就会知道,group维护了一个EventLoop 数组(EventExecutor[] executors,数组的初始化是通过group的来实现的,这个具体的是怎么分配后续单独做说明),group通过一定的算法去做具体的分配,这些都将在后续单独分析group源码的时候再进行详细说明。

    public EventLoop next() {
        return (EventLoop) super.next();
    }

结合第一部分,可知channel的注册和端口的绑定都是通过group属性(也就是boss)分配的同一个EventLoop来实现的,以及接收客户端请求也是在这个实例的run方法中。由此可知AbstractBootstrap中的group属性(也就是boss)会根据一定算法分配一个EventLoop实例来处理channel的注册、端口绑定以及客户端请求。
3、由第一部分可知,处理客户端请求在EventLoop实例的processSelectedKeys()方法中,这个方法最终都会调用到如下方法。

 private void processSelectedKeysOptimized() {
     for (int i = 0; i < selectedKeys.size; ++i) {
           final Object a = k.attachment();
           //这里是通过附件获取到的channel,注册的时候我们已经分析了
           //会把当前的NioServerSocketChannel实例通过附件的形式注册到底层channel上
            //所以当前的AbstractNioChannel就是一个NioServerSocketChannel实例
             processSelectedKey(k, (AbstractNioChannel) a);
     }
 }
 private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
     final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
     //所以这里的实例同样是NioServerSocketChannel实例,由此可知ch.unsafe()方法返回的是
     //new NioMessageUnsafe()实例,这在[第三节](https://blog.csdn.net/solayang/article/details/116641699)中已经分析过了
     try {
        if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0
         || readyOps == 0) {
             unsafe.read();//客户端的accept和读都是走这里
         }

 }

所以这里的unsafe.read()走的是NioMessageUnsafe实例中的read方法,这在第一部分也分析过了,最后会调用ServerBootstrapAcceptor的channelRead方法,这个方法主要做以下几件事:
1)往childChannel中添加自定义的childHandler
2)设置子参数和子属性
3)通过childGroup注册childChannel,这里的注册原理和group的注册原理是一样的,唯一不同的一点是当前的channel是NioSocketChannel实例,然后doRegister方法中底层的channel是SocketChannel而不是ServerSocketChannel。这里要注意的是,childGroup和group一样会分配一个EventLoop实例来处理当前的注册并开启当前线程。
到这里我们先整理一下目前的一个处理流程:

  1. 从bind()开始,先实例化NioServerSocketChannel,然后初始化channel,并把childGroup作为构造参数赋值给ServerBootstrapAcceptor,然后把这个handler添加的channel的pipeline中
  2. 注册channel,group分配一个EventLoop实例(并赋值给channel),通过这个实例把当前的channel作为附件注册到底层ServerSocketChannel上(这里就涉及到NIO编程),并开启线程
  3. 端口绑定,端口绑定也是通过channel的eventLoop属性来实现,最后调用底层的端口绑定的方法。
  4. 接收客户端请求,客服端请求在EventLoop实例的run方法中,先生成一个NioSocketChannel实例对象,调用ServerBootstrapAcceptor的channelRead方法,通过childGroup注册childChannel,等待客户端发送数据
  5. 通过自定义的handler处理客户端的数据

所以通过上边的反复分析,可以明确group会分配一个EventLoop实例用来绑定端口以及处理来自当前端口的连接请求。group处理完请求之后,childGroup会分配一个EventLoop来处理客户端的读写事件。
所以netty处理请求的大致逻辑就分析到此。
以上,有任何不对的地方,请指正,敬请谅解。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菜鸟+1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值