小白带你认识netty(二)之netty服务端启动(下)

承接上一篇小白带你认识netty(二)之netty服务端启动(上),还剩下两步骤:三、注册Selector:将Channel注册到Selector上 和 四、端口的绑定:服务端端口的监听。java

三、注册Selector:将Channel注册到Selector上数组

分析完init方法,继续分析ChannelFuture regFuture = config().group().register(channel);promise

看下config方法,本对象中该方法是abstract,即调用子类的config方法,由于AbstractBootstrap的子类的是ServerBootstrap,所以进入ServerBootstrap看下:安全

那么问题来了,这个config是在哪初始化的呢?oop

原来是定义的时候就已经初始化了。看下该类的group方法:优化

原来仍是调用AbstractBootstrap类的group方法,咱们知道,这里的group是bossEventLooGroup,还记的吗?不记得就再看一便上一章哦。。所以这个register方法是调用的NioEventLoopGoup类里的register方法。由于NioEventLoopGoup没有该register方法,因此就调用了其父类MultithreadEventLoopGroup类中的register方法。this

看下这个next方法,spa

跟进父类的next方法:.net

看下这个chooser是啥?线程

还记得第一章中的EventLoop数组的选择器么?有划红线强调的哦。。。此next方法就是在NioEvenetLoopGroup中的EventLoop数组中选出一个EventLoop对象来。所以此处的register方法应该是EventLoop中的方法。由于EventLoop没有该方法,即调用父类SingleThreadEventLoop的register方法。

这里unsafe方法很明显是调用NioServerSocketChannel中的unsafe方法。继续跟进:

还记的这个unsafe的初始化么?就是在AbstractChannl的构造方法中初始化的,下图证:

因此register方法是DefaultServerUnsafe类中的,由于该类没有register方法,因此调用的是该类的父类AbstractUnsafe类中的方法:

首先经过AbstractChannel.this.eventLoop = eventLoop;将线程绑定到了NioServerSocketChannel上,即NioServerSocketChannel实例化了eventLoop。所以上一章的:

这里ch.eventLoop就不为null了。继续eventLoop的inEventLoop,一直向NioEventLoop的父类上追,追到了AbstractEventExecutor类,看下该类的inEventLoop:

继续跟进:

thread是当前线程,那this.thread是啥???由于咱们在分析NioEventLoop并无看到这个this.thread的实例化,因此这里是null,所以这个条件返回false,走的else的逻辑。

看下这个eventLoop.execute方法:

同理,inEventLoop返回的是false,因此,应该是走else的逻辑:

使用cas无锁化执行doStartThread()方法。

这个executor是哪来的?咱们在分析NioEventLoopGroup的时候,知道了会初始化一个线程执行器:

所以executor是执行的ThreadPerTaskExecutor类里的execute方法:

所以executor.execute是建立一个线程执行run方法。在上面的run方法中,thread = Thread.currentThread();将当前的线程赋值给了NioEventLoop对象的thread变量,所以只要是该NioEventLoop对象的inEventLoop方法都是返回的true了。注意是NioEventLoop而不是NioEventLoopGroup。接着就是执行SingleThreadEventExecutor.this.run();方法了,这个方法咱们后面会分析,这里不作分析。再次回到

这个方法上,暂时咱们只要知道startThread是为NioEventLoop建立一个线程。在多说一句,这个线程是一直处于循环状态的。咱们继续看addTask方法:

这个方法很简单,就是将task丢进队列中,以下图:

这个taskQueue没有忘吧,就是netty优化的mpsc队列。OK既然把register0方法的调用放在了taskQueue中,总之会被调用的,看下register0:

先看下doRegister方法:

这里的javaChannel就是jdk原生的ServerSocketChannel对象了。因此真相就在这里,这里是原生的jdk注册方法,将ServerSocketChannel注册到NioEventLoop的select上,注意第三个参数为0.

至此注册selector过程结束。

四、端口的绑定

回到doBind方法:

以上三步分析完了initAndRegister方法,继而咱们进入doBind0这个方法。

有了上面的基础,咱们很容易知道调用的NioEvenetLoop对象的execute方法,即将该run方法放入到NioEventLoop的taskQueue中,最终被执行。

下面分析下channel.bind方法,系好安全带,由于没有pipeline的知识点,因此会感受开车比较快。

调用NioServerSocketChannel的bind方法,

继续跟进:

继续:

继续跟进:next.invokeBind(localAddress, promise);

继续跟进,就是HeadContext中的bind方法:

看下这个unsafe是啥?

就是NioServerSocket中的NioMessageUnsafe,即调用的仍是AbstractUnSafe类中的方法:

进入doBind方法:

至此,绑定的代码就真相大白了,就是调用了jdk原生的bind方法。

这里还有一个小点,以前看到的,向selector注册的事件是0啊,并无注册accept事件啊。。这里,咱们回到bind方法:

分析完了了doBind方法,咱们继续向下看:

这里会执行pipeline.fireChannelActive();方法,跟进去,一样系好安全带哦:

继续:

继续跟进next.invokeChannelInactive();

这里的channelInactive会调用HeadContext类的channelInactive方法:

第一个方法,则是调用ChannelActive事件,后面详细说。这里咱们重点下面的方法:readIfIsAutoRead。

继续:

继续跟进read:

继续跟进:

继续:

而后跟进invokeRead方法:

而后调用HeadContext的read方法:

卧槽,绕了一堆,仍是调用AbastractUnSafe的beginRead方法:

继续跟进doBeginRead方法:

还记得这个selectionKey么?就是上面红字强调的:

再向selector注册NioServerSocket的时候,注册的事件是0,所以if ((interestOps & readInterestOp) == 0)这个判断返回的是true。那么readInterestOp这个哪来的?还记的上一章的accept事件么?

一次次调用父类方法,放在AbstractNioChannel类里面了。所以:

这里的逻辑就是向selector注册accept事件。

OK,到这里,netty的启动过程就分析完了,虽然中间有些方法还不清楚什么时候被触发,但老是知道一个启动流程了。总结下这启动步骤:

newChannel():建立NioServerSocketChannel对象

init():初始化NioServerSocketChannel对象

register():向Selector中注册NioServerSocketChannel对象

doBind():对本地端口进行监听,监听成功后,从新向selector注册OP_ACCEPT事件。

相关文章
相关标签/搜索