小白帶你認識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事件。

相關文章
相關標籤/搜索