• 68046

    文章

  • 643

    评论

  • 65

    友链

  • 最近新加了换肤功能,大家多来逛逛吧~~~~
  • 喜欢这个网站的朋友可以加一下QQ群,我们一起交流技术。

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

撸了今年阿里、腾讯和美团的面试,我有一个重要发现.......>>

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

3、注册Selector:将Channel注册到Selector上

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

看下config方法,本对象中该方法是abstract,即调用子类的config方法,因为AbstractBootstrap的子类的是ServerBootstrap,因此进入ServerBootstrap看下:

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

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

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

看下这个next方法,

跟进父类的next方法:

看下这个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过程结束。

4、端口的绑定

回到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事件。


 转载至链接:https://my.oschina.net/littlestyle/blog/2877896。

695856371Web网页设计师②群 | 喜欢本站的朋友可以收藏本站,或者加入我们大家一起来交流技术!

欢迎来到梁钟霖个人博客网站。本个人博客网站提供最新的站长新闻,各种互联网资讯。 还提供个人博客模板,最新最全的java教程,java面试题。在此我将尽我最大所能将此个人博客网站做的最好! 谢谢大家,愿大家一起进步!

转载原创文章请注明出处,转载至: 梁钟霖个人博客www.liangzl.com

1条评论

Loading...


发表评论

电子邮件地址不会被公开。 必填项已用*标注

自定义皮肤
注册梁钟霖个人博客