自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

原來 Netty 的核心啟動邏輯是這樣的!

開發(fā) 前端
bind 的很多方法都是異步執(zhí)行的,所以有些流程是主線程執(zhí)行,有些是 eventloop 執(zhí)行的,這個需要注意下,不然會感覺有點亂。

你好,我是yes。

上篇我們已經(jīng)了解了 Netty 的啟動流程,還剩一個 bind 方法沒有細講,這篇我們就著重的說下 bind 方法,這個方法也是觸發(fā) Netty 真正啟動的方法。

先打個預(yù)防針,源碼也不是那么簡單的,有時候看著有點繞,如果你想面試的時候胸有成竹,還是得有點耐心的,如果中間沒看懂沒事,最后我有總結(jié),看完總結(jié)之后應(yīng)該會清晰的。

對了,如果有條件的話,建議在電腦上看這篇文章,會更加舒適。

好了,開局先來一張圖,bind 的核心操作就如下圖所示,下面長篇的源碼分析也是為了說清楚這個流程,所以什么類名,方法名都不重要,重要的是知曉整體流程:

注意,上圖的 Channel 指的是 ServerChannel。

bind 的很多方法都是異步執(zhí)行的,所以有些流程是主線程執(zhí)行,有些是 eventloop 執(zhí)行的,這個需要注意下,不然會感覺有點亂。

先看看這張圖大體有個印象,然后我們開始盤 bind 方法~

bind 流程

從 bind 方法進入,實際上就會調(diào)用父類 AbstractBootstrap#doBind。

我們來簡單的看下 doBind 這個方法,要點我都用文字標明了:

可以看到,這個方法主要做了下面這四件事:

  • 創(chuàng)建 channel
  • 初始化 channel
  • 綁定 channel 至 group 內(nèi)的某個 eventLoop 上
  • 綁定端口

下面我會逐一的分析。

創(chuàng)建 channel

先來看下 initAndRegister 方法。

從下面的源碼可以看到,這個方法主要就是創(chuàng)建一個 Channel 對象,然后初始化 Channel,最后將它注冊到 eventLoop 上。

channelFactory.newChannel() 是利用反射得到一個 Channel 對象。還記得我們構(gòu)建 ServerBootstrap 時候設(shè)置的 channel 類型嗎:.channel(NioServerSocketChannel.class)

通過傳入的這個 class 就可以得到構(gòu)造器,然后調(diào)用 newInstance 即可得到一個對象。

這樣就創(chuàng)建了一個 NioServerSocketChannel 對象。

通過繼承鏈,我們可以發(fā)現(xiàn) NioServerSocketChannel 會調(diào)用父類 AbstractChannel 的構(gòu)造器,而在這里就會創(chuàng)建三個重要的東西:

  • id,channel 的標識
  • unsafe,用來操作底層數(shù)據(jù)讀寫
  • pipeline,handler的編排

所以,從這里可以得知,創(chuàng)建一個 Channel 配套就會新建一個 pipeline,即每個 Channel 都有自己的 pipeline。

初始化 channel

創(chuàng)建完 Channel 的操作之后,緊接著就初始化 Channel ,即 init() 方法。

可以看到,初始化首先就是把之前在 ServerBootstrap 時配置的 option 和 attr 塞到已經(jīng)創(chuàng)建的 ServerSocketChannel 中,這個很好理解。

然后往 ServerSocketChannel 的 pipeline 中塞入一個 ChannelInitializer。

那 ChannelInitializer 是個什么玩意?它其實是一個抽象類,且是一個入站的 handler。

不過它是一個特殊的 ChannelHandler ,從 ChannelInitializer 類的注釋就知道:

它的使命就是簡化注冊完畢后的初始化操作,可以理解為是一個中轉(zhuǎn)的 handler,一個工具類。它在完成初始化動作之后會從 pipeline 中被移除。

所以,利用 ChannelInitializer 將初始化邏輯封裝起來,當 channel 注冊到 eventLoop 中之后會觸發(fā)事件,然后就會調(diào)用 ChannelInitializer#initChannel 來執(zhí)行初始化,僅此而已。

我們可以看到,上面 initChannel 邏輯是先添加之前配置給 ServerSocketChannel 的 handler,在我們的 helloworld 示例中就是添加 LoggingHandler。

然后再異步添加一個 ServerBootstrapAcceptor 這個 handler,從名字來看,這個 handler 主要是接收處理新連接。

小貼士:此時的 initChannel 邏輯還未執(zhí)行,是要等到后面事件觸發(fā)了才會執(zhí)行,且執(zhí)行的線程是 eventLoop 線程。

好了,看到這肯定有人會有疑問,為什么 initChannel 里面要異步添加 ServerBootstrapAcceptor?

為什么要異步添加 ServerBootstrapAcceptor?不直接添加?

其實源碼注釋說明的很清楚(上面為了清晰結(jié)構(gòu),把注釋都刪了)

簡單翻譯下,就是用戶可能會用 ChannelInitializer 來設(shè)置 ServerSocketChannel 的 handler ,注意是ServerSocketChannel 的 handler,不是那個 childHandler 哈。

來看下示例代碼:

  1. // 這樣是沒問題的,不用異步添加 ServerBootstrapAcceptor 
  2.  ServerBootstrap sb = new ServerBootstrap(); 
  3.     sb.channel(...).group(...).childHandler(...).handler(ourHandler); 
  4.      
  5. //這樣的就需要異步添加 ServerBootstrapAcceptor 
  6. ServerBootstrap sb = new ServerBootstrap(); 
  7. sb.channel(...).group(...).childHandler(...).handler( 
  8.     new ChannelInitializer<Channel>() { 
  9.         @Override 
  10.         protected void initChannel(Channel ch) throws Exception { 
  11.             ChannelPipeline pipeline = ch.pipeline(); 
  12.             pipeline.addLast(ourHandler); 
  13.         } 
  14.     } 
  15. ); 

因為在利用 ChannelInitializer 設(shè)置 handler 的情況下,initChannel(…)方法只會在該方法(init 內(nèi)添加ServerBootstrapAcceptor的方法)返回后被調(diào)用。

因此,需要確保以延遲的方式添加,使得用戶定義的 handler 都放在 ServerBootstrapAcceptor 前面。

簡單地說,就是讓 ServerBootstrapAcceptor 成為 ServerSocketChannel 的 pipeline 中最后一個 inboundHandler,這樣用戶定義的 handler 邏輯才會被調(diào)用到。

因為當事件傳遞到 ServerBootstrapAcceptor 過就不會再繼續(xù)通過 pipeline 傳遞了,會將接待的子 channel 直接分配給 workerGroup了,如果用戶自定義的 handler 在 ServerBootstrapAcceptor 后面的話,里面的邏輯是不會被執(zhí)行的,等于白加。

不理解的可以多讀幾遍上面的話哈,有一點點小繞。

都說到這了,那就來看看 ServerBootstrapAcceptor 的內(nèi)部邏輯吧。

ServerBootstrapAcceptor 的內(nèi)部邏輯

很簡單,就是根據(jù) selector 得到新連接對應(yīng)的 channel(子channel),然后為其配置之前(初始化ServerBootstrap時)設(shè)置的 childhandler、childoption、childattr,緊接著從 workerGroup 中選擇一個 eventLoop ,將 channel 注冊到這個 eventLoop 上:

這樣,新建的子 channel 之后的所有事件(讀、寫等 I/O 事件),都由從 workerGroup 中選定的那個 eventLoop 負責。

至此,我們講完了 init(channel) 的操作。

channel 注冊至 eventLoop

創(chuàng)建且初始化完 channel 之后,就需要把已經(jīng)準備完畢的 channel 注冊到一個 eventLoop 中。

即上面的ChannelFuture regFuture = config().group().register(channel);(從返回值可以得知這是一個異步執(zhí)行流程)

這個動作就是從 bossGroup 中選一個 EventLoop ,然后將 channel 注冊到選定的 EventLoop 上。

這個 next() 實際就是調(diào)用我們之前提到的 chooser 來選擇一個 eventLoop,最終會將此 eventLoop 傳遞到 AbstractUnsafe#register 中,執(zhí)行注冊邏輯,核心就在這個 register0 方法。

可以看到,無論如何都是由 eventLoop 線程來執(zhí)行 register0 操作(所以對主線程而言,這是異步的)。

我們來看下 register0 都做了什么事:

  • 調(diào)用底層接口,將 channel 注冊到 selector 上
  • 觸發(fā)之前配置的 ChannelInitializer#initChannel
  • 異步添加綁定端口的任務(wù)到 eventLoop 中
  • 觸發(fā) Registered 事件,使之在 pipeline 上傳遞

我們先看第一步 doRegister,看看具體是怎么注冊 Channel 至 Selector 上的。

因為我們都知道 ServerSocketChannel 是 Netty 定義的類,和 JDK 沒任何關(guān)系,那如何與 JDK 的類適配呢?到底是如何注冊到 JDK 的 Selector 上的呢?

看到我圈起來的地方?jīng)],實際會把之前創(chuàng)建的 JDK 的 Channel 注冊到 Selector 上,而 Netty 的 Channel 會作為一個 attachment 綁定到 JDK 的 Channel 上。

這樣一來,每次 Selector 的時候,如對應(yīng)的 JDK Channel 有事件發(fā)生,則 Netty 都可以從返回的 JDK Channel 的 attachment 中獲取自己的 Channel,然后觸發(fā)后續(xù)的邏輯,這招叫移花接木。

然后再來看第二步 pipeline.invokeHandlerAddedIfNeeded()。

此時才會調(diào)用 ChannelInitializer#initChannel 來添加 handler,且異步提交了一個添加 ServerBootstrapAcceptor 的任務(wù),隨后將 ChannelInitializer 從 pipeline 中移除。

緊接著就是觸發(fā) Registered 事件,這個事件會隨著 pipeline 傳播至所有 handler,只要是入站的 handler,且實現(xiàn)了下面這個方法就會被調(diào)用,所以如果你想在注冊完畢之后做一些邏輯處理,那么就可以創(chuàng)建一個 handler 且實現(xiàn) channelRegistered 方法。

好了,至此我們終于把 Channel 的創(chuàng)建、初始化、注冊給說完了,后面就是綁定的端口的操作了。

綁定端口

綁定端口主要有兩個事情需要關(guān)注下,一個是調(diào)用底層 JDK 綁定端口的實現(xiàn),二是綁定完之后觸發(fā) active 事件,表明 channel 一切都已就緒。

底層 JDK Channel 的 bind 方法,我就不說了,這個觸發(fā) active 事件比較關(guān)鍵,最終會觸發(fā) AbstractNioChannel#doBeginRead,注冊 accept 事件,這樣 ServerSocketChannel 感興趣事件也注冊完畢,可以干活了!

好了,現(xiàn)在我們再來看下這張圖,來對整體的過程捋一捋,至于上面的那些代碼理不清沒事,你只要看懂下面這種圖,知曉大體的流程就夠了:

現(xiàn)在看這張圖是不是有不一樣的感覺?其實Netty 服務(wù)端的啟動流程也就這么回事兒,我再用語言組織一下:

  • 創(chuàng)建 ServerSocketChannel(配套指定一個ID、底層操作 unsafe對象、pipeline)
  • 將配置的option、attr設(shè)置在創(chuàng)建的 ServerSocketChannel 上
  • 添加一個 ChannelInitializer 至 ServerSocketChannel 的 pipeline 中
  • 從 bossGroup 中選擇一個 eventLoop,將 ServerSocketChannel 與之綁定,且之后生命周期內(nèi)的操作都由這個 eventLoop 執(zhí)行
  • 觸發(fā)第三步添加的 ChannelInitializer 的 initChannel 方法,將用戶配置的 handler 添加到 ServerSocketChannel 的 pipeline 中,且由于需要保證框架內(nèi)置的 ServerBootstrapAcceptor 這個 handler 處于 inboundhandler 的最后一個,因此是異步添加。
  • 觸發(fā) registered 事件,使之在 pipeline 上傳播
  • 調(diào)用 JDK 底層方法,綁定端口
  • 觸發(fā) active 事件,注冊 ServerSocketChannel 感興趣的事件(OP_ACCEPT),用于接收新連接
  • over

最后

好了,我相信通過最后一張圖和最后一段話,你應(yīng)該大致了解了整個 Netty 的啟動流程。

如果不明白的話再看看圖,最后是在電腦上看哈,看著代碼應(yīng)該不難理解的,重點就是分清上面的 Channel 指的是 Server 端的 Channel,然后主線程和 eventLoop 線程的執(zhí)行邏輯,有條件的建議自己打斷點試試。

下篇我們將談?wù)?Netty 的 Reactor 模型,這玩意面試被問的幾率好像挺大,如果你還不清楚,沒事,下篇我們慢慢盤!

 

我是 yes,從一點點到億點點,我們下篇見~

 

責任編輯:武曉燕 來源: yes的練級攻略
相關(guān)推薦

2022-05-09 08:37:43

IO模型Java

2022-12-14 07:32:40

InnoDBMySQL引擎

2020-06-08 17:35:27

Redis集群互聯(lián)網(wǎng)

2009-03-11 14:42:57

面試求職案例

2023-05-08 07:52:29

JSXReactHooks

2021-11-10 09:45:06

Lambda表達式語言

2024-12-17 12:00:00

C++對象模型

2018-04-02 15:13:21

網(wǎng)絡(luò)

2024-04-30 08:22:51

Figma圖形編輯變換矩陣

2017-01-05 15:07:33

2023-02-15 08:17:38

2025-02-17 09:22:16

MySQLSQL語句

2020-05-26 08:52:36

Java JVM多態(tài)

2022-05-05 08:55:12

工業(yè)物聯(lián)網(wǎng)IIoT

2017-01-16 13:34:21

2024-02-06 09:30:25

Figma矩形矩形物理屬性

2023-05-22 15:58:11

2016-10-12 08:54:24

2024-05-20 08:45:46

2020-11-24 06:20:02

Linux日志文件系統(tǒng)
點贊
收藏

51CTO技術(shù)棧公眾號