原來 Netty 的核心啟動(dòng)邏輯是這樣的!
你好,我是yes。
上篇我們已經(jīng)了解了 Netty 的啟動(dòng)流程,還剩一個(gè) bind 方法沒有細(xì)講,這篇我們就著重的說下 bind 方法,這個(gè)方法也是觸發(fā) Netty 真正啟動(dòng)的方法。
先打個(gè)預(yù)防針,源碼也不是那么簡單的,有時(shí)候看著有點(diǎn)繞,如果你想面試的時(shí)候胸有成竹,還是得有點(diǎn)耐心的,如果中間沒看懂沒事,最后我有總結(jié),看完總結(jié)之后應(yīng)該會(huì)清晰的。
對了,如果有條件的話,建議在電腦上看這篇文章,會(huì)更加舒適。
好了,開局先來一張圖,bind 的核心操作就如下圖所示,下面長篇的源碼分析也是為了說清楚這個(gè)流程,所以什么類名,方法名都不重要,重要的是知曉整體流程:
注意,上圖的 Channel 指的是 ServerChannel。
bind 的很多方法都是異步執(zhí)行的,所以有些流程是主線程執(zhí)行,有些是 eventloop 執(zhí)行的,這個(gè)需要注意下,不然會(huì)感覺有點(diǎn)亂。
先看看這張圖大體有個(gè)印象,然后我們開始盤 bind 方法~
bind 流程
從 bind 方法進(jìn)入,實(shí)際上就會(huì)調(diào)用父類 AbstractBootstrap#doBind。
我們來簡單的看下 doBind 這個(gè)方法,要點(diǎn)我都用文字標(biāo)明了:
可以看到,這個(gè)方法主要做了下面這四件事:
- 創(chuàng)建 channel
- 初始化 channel
- 綁定 channel 至 group 內(nèi)的某個(gè) eventLoop 上
- 綁定端口
下面我會(huì)逐一的分析。
創(chuàng)建 channel
先來看下 initAndRegister 方法。
從下面的源碼可以看到,這個(gè)方法主要就是創(chuàng)建一個(gè) Channel 對象,然后初始化 Channel,最后將它注冊到 eventLoop 上。
channelFactory.newChannel() 是利用反射得到一個(gè) Channel 對象。還記得我們構(gòu)建 ServerBootstrap 時(shí)候設(shè)置的 channel 類型嗎:.channel(NioServerSocketChannel.class)
通過傳入的這個(gè) class 就可以得到構(gòu)造器,然后調(diào)用 newInstance 即可得到一個(gè)對象。
這樣就創(chuàng)建了一個(gè) NioServerSocketChannel 對象。
通過繼承鏈,我們可以發(fā)現(xiàn) NioServerSocketChannel 會(huì)調(diào)用父類 AbstractChannel 的構(gòu)造器,而在這里就會(huì)創(chuàng)建三個(gè)重要的東西:
- id,channel 的標(biāo)識(shí)
- unsafe,用來操作底層數(shù)據(jù)讀寫
- pipeline,handler的編排
所以,從這里可以得知,創(chuàng)建一個(gè) Channel 配套就會(huì)新建一個(gè) pipeline,即每個(gè) Channel 都有自己的 pipeline。
初始化 channel
創(chuàng)建完 Channel 的操作之后,緊接著就初始化 Channel ,即 init() 方法。
可以看到,初始化首先就是把之前在 ServerBootstrap 時(shí)配置的 option 和 attr 塞到已經(jīng)創(chuàng)建的 ServerSocketChannel 中,這個(gè)很好理解。
然后往 ServerSocketChannel 的 pipeline 中塞入一個(gè) ChannelInitializer。
那 ChannelInitializer 是個(gè)什么玩意?它其實(shí)是一個(gè)抽象類,且是一個(gè)入站的 handler。
不過它是一個(gè)特殊的 ChannelHandler ,從 ChannelInitializer 類的注釋就知道:
它的使命就是簡化注冊完畢后的初始化操作,可以理解為是一個(gè)中轉(zhuǎn)的 handler,一個(gè)工具類。它在完成初始化動(dòng)作之后會(huì)從 pipeline 中被移除。
所以,利用 ChannelInitializer 將初始化邏輯封裝起來,當(dāng) channel 注冊到 eventLoop 中之后會(huì)觸發(fā)事件,然后就會(huì)調(diào)用 ChannelInitializer#initChannel 來執(zhí)行初始化,僅此而已。
我們可以看到,上面 initChannel 邏輯是先添加之前配置給 ServerSocketChannel 的 handler,在我們的 helloworld 示例中就是添加 LoggingHandler。
然后再異步添加一個(gè) ServerBootstrapAcceptor 這個(gè) handler,從名字來看,這個(gè) handler 主要是接收處理新連接。
小貼士:此時(shí)的 initChannel 邏輯還未執(zhí)行,是要等到后面事件觸發(fā)了才會(huì)執(zhí)行,且執(zhí)行的線程是 eventLoop 線程。
好了,看到這肯定有人會(huì)有疑問,為什么 initChannel 里面要異步添加 ServerBootstrapAcceptor?
為什么要異步添加 ServerBootstrapAcceptor?不直接添加?
其實(shí)源碼注釋說明的很清楚(上面為了清晰結(jié)構(gòu),把注釋都刪了)
簡單翻譯下,就是用戶可能會(huì)用 ChannelInitializer 來設(shè)置 ServerSocketChannel 的 handler ,注意是ServerSocketChannel 的 handler,不是那個(gè) childHandler 哈。
來看下示例代碼:
- // 這樣是沒問題的,不用異步添加 ServerBootstrapAcceptor
- ServerBootstrap sb = new ServerBootstrap();
- sb.channel(...).group(...).childHandler(...).handler(ourHandler);
- //這樣的就需要異步添加 ServerBootstrapAcceptor
- ServerBootstrap sb = new ServerBootstrap();
- sb.channel(...).group(...).childHandler(...).handler(
- new ChannelInitializer<Channel>() {
- @Override
- protected void initChannel(Channel ch) throws Exception {
- ChannelPipeline pipeline = ch.pipeline();
- pipeline.addLast(ourHandler);
- }
- }
- );
因?yàn)樵诶?ChannelInitializer 設(shè)置 handler 的情況下,initChannel(…)方法只會(huì)在該方法(init 內(nèi)添加ServerBootstrapAcceptor的方法)返回后被調(diào)用。
因此,需要確保以延遲的方式添加,使得用戶定義的 handler 都放在 ServerBootstrapAcceptor 前面。
簡單地說,就是讓 ServerBootstrapAcceptor 成為 ServerSocketChannel 的 pipeline 中最后一個(gè) inboundHandler,這樣用戶定義的 handler 邏輯才會(huì)被調(diào)用到。
因?yàn)楫?dāng)事件傳遞到 ServerBootstrapAcceptor 過就不會(huì)再繼續(xù)通過 pipeline 傳遞了,會(huì)將接待的子 channel 直接分配給 workerGroup了,如果用戶自定義的 handler 在 ServerBootstrapAcceptor 后面的話,里面的邏輯是不會(huì)被執(zhí)行的,等于白加。
不理解的可以多讀幾遍上面的話哈,有一點(diǎn)點(diǎn)小繞。
都說到這了,那就來看看 ServerBootstrapAcceptor 的內(nèi)部邏輯吧。
ServerBootstrapAcceptor 的內(nèi)部邏輯
很簡單,就是根據(jù) selector 得到新連接對應(yīng)的 channel(子channel),然后為其配置之前(初始化ServerBootstrap時(shí))設(shè)置的 childhandler、childoption、childattr,緊接著從 workerGroup 中選擇一個(gè) eventLoop ,將 channel 注冊到這個(gè) eventLoop 上:
這樣,新建的子 channel 之后的所有事件(讀、寫等 I/O 事件),都由從 workerGroup 中選定的那個(gè) eventLoop 負(fù)責(zé)。
至此,我們講完了 init(channel) 的操作。
channel 注冊至 eventLoop
創(chuàng)建且初始化完 channel 之后,就需要把已經(jīng)準(zhǔn)備完畢的 channel 注冊到一個(gè) eventLoop 中。
即上面的ChannelFuture regFuture = config().group().register(channel);(從返回值可以得知這是一個(gè)異步執(zhí)行流程)
這個(gè)動(dòng)作就是從 bossGroup 中選一個(gè) EventLoop ,然后將 channel 注冊到選定的 EventLoop 上。
這個(gè) next() 實(shí)際就是調(diào)用我們之前提到的 chooser 來選擇一個(gè) eventLoop,最終會(huì)將此 eventLoop 傳遞到 AbstractUnsafe#register 中,執(zhí)行注冊邏輯,核心就在這個(gè) register0 方法。
可以看到,無論如何都是由 eventLoop 線程來執(zhí)行 register0 操作(所以對主線程而言,這是異步的)。
我們來看下 register0 都做了什么事:
- 調(diào)用底層接口,將 channel 注冊到 selector 上
- 觸發(fā)之前配置的 ChannelInitializer#initChannel
- 異步添加綁定端口的任務(wù)到 eventLoop 中
- 觸發(fā) Registered 事件,使之在 pipeline 上傳遞
我們先看第一步 doRegister,看看具體是怎么注冊 Channel 至 Selector 上的。
因?yàn)槲覀兌贾?ServerSocketChannel 是 Netty 定義的類,和 JDK 沒任何關(guān)系,那如何與 JDK 的類適配呢?到底是如何注冊到 JDK 的 Selector 上的呢?
看到我圈起來的地方?jīng)],實(shí)際會(huì)把之前創(chuàng)建的 JDK 的 Channel 注冊到 Selector 上,而 Netty 的 Channel 會(huì)作為一個(gè) attachment 綁定到 JDK 的 Channel 上。
這樣一來,每次 Selector 的時(shí)候,如對應(yīng)的 JDK Channel 有事件發(fā)生,則 Netty 都可以從返回的 JDK Channel 的 attachment 中獲取自己的 Channel,然后觸發(fā)后續(xù)的邏輯,這招叫移花接木。
然后再來看第二步 pipeline.invokeHandlerAddedIfNeeded()。
此時(shí)才會(huì)調(diào)用 ChannelInitializer#initChannel 來添加 handler,且異步提交了一個(gè)添加 ServerBootstrapAcceptor 的任務(wù),隨后將 ChannelInitializer 從 pipeline 中移除。
緊接著就是觸發(fā) Registered 事件,這個(gè)事件會(huì)隨著 pipeline 傳播至所有 handler,只要是入站的 handler,且實(shí)現(xiàn)了下面這個(gè)方法就會(huì)被調(diào)用,所以如果你想在注冊完畢之后做一些邏輯處理,那么就可以創(chuàng)建一個(gè) handler 且實(shí)現(xiàn) channelRegistered 方法。
好了,至此我們終于把 Channel 的創(chuàng)建、初始化、注冊給說完了,后面就是綁定的端口的操作了。
綁定端口
綁定端口主要有兩個(gè)事情需要關(guān)注下,一個(gè)是調(diào)用底層 JDK 綁定端口的實(shí)現(xiàn),二是綁定完之后觸發(fā) active 事件,表明 channel 一切都已就緒。
底層 JDK Channel 的 bind 方法,我就不說了,這個(gè)觸發(fā) active 事件比較關(guān)鍵,最終會(huì)觸發(fā) AbstractNioChannel#doBeginRead,注冊 accept 事件,這樣 ServerSocketChannel 感興趣事件也注冊完畢,可以干活了!
好了,現(xiàn)在我們再來看下這張圖,來對整體的過程捋一捋,至于上面的那些代碼理不清沒事,你只要看懂下面這種圖,知曉大體的流程就夠了:
現(xiàn)在看這張圖是不是有不一樣的感覺?其實(shí)Netty 服務(wù)端的啟動(dòng)流程也就這么回事兒,我再用語言組織一下:
- 創(chuàng)建 ServerSocketChannel(配套指定一個(gè)ID、底層操作 unsafe對象、pipeline)
- 將配置的option、attr設(shè)置在創(chuàng)建的 ServerSocketChannel 上
- 添加一個(gè) ChannelInitializer 至 ServerSocketChannel 的 pipeline 中
- 從 bossGroup 中選擇一個(gè) eventLoop,將 ServerSocketChannel 與之綁定,且之后生命周期內(nèi)的操作都由這個(gè) eventLoop 執(zhí)行
- 觸發(fā)第三步添加的 ChannelInitializer 的 initChannel 方法,將用戶配置的 handler 添加到 ServerSocketChannel 的 pipeline 中,且由于需要保證框架內(nèi)置的 ServerBootstrapAcceptor 這個(gè) handler 處于 inboundhandler 的最后一個(gè),因此是異步添加。
- 觸發(fā) registered 事件,使之在 pipeline 上傳播
- 調(diào)用 JDK 底層方法,綁定端口
- 觸發(fā) active 事件,注冊 ServerSocketChannel 感興趣的事件(OP_ACCEPT),用于接收新連接
- over
最后
好了,我相信通過最后一張圖和最后一段話,你應(yīng)該大致了解了整個(gè) Netty 的啟動(dòng)流程。
如果不明白的話再看看圖,最后是在電腦上看哈,看著代碼應(yīng)該不難理解的,重點(diǎn)就是分清上面的 Channel 指的是 Server 端的 Channel,然后主線程和 eventLoop 線程的執(zhí)行邏輯,有條件的建議自己打斷點(diǎn)試試。
下篇我們將談?wù)?Netty 的 Reactor 模型,這玩意面試被問的幾率好像挺大,如果你還不清楚,沒事,下篇我們慢慢盤!
我是 yes,從一點(diǎn)點(diǎn)到億點(diǎn)點(diǎn),我們下篇見~