Netty系列(四):NioServerSocketChannel註冊

mars_jun發表於2019-02-10

前言

本文主要介紹的是服務端NioServerSocketChannel建立和註冊流程以及客戶端連線到服務端後的NioSocketChannel的建立和註冊流程,這兩步都是很關鍵的。在介紹的過程中,中間會穿插著進行ChannelHandler與ChannelPipeline的一些簡單的介紹。


服務端程式碼

上面的程式碼段我已經新增了詳細的註釋,具體的註冊流程得從我標紅的bind這個方法開始,我們隨著這條線追蹤一下,會發現最終會呼叫到doBind方法,裡面有個initAndRegister函式,從這裡開始就正式進入建立註冊流程了。


initAndRegister函式

在這個函式中,我們主要關注三個點:

  1. 呼叫NioServerSocketChannel的無參建構函式進行其例項的初始化。
  2. 進行Option以及Attr的設定,這個方法裡面,大家可以關注一下我下面圈紅的ServerBootstrapAcceptor這個類,它會被註冊到NioServerSocketChannel的pipeline中去,後面SocketChannel的註冊流程會依靠這個類做一些操作,大家先留個印象即可,重點
  3. 開啟NioEventLoop的執行緒,並執行register操作。

我們直接從第三步開始說,涉及到前面的知識點我後面會一一解釋。

server_channel_register.png
server_channel_register.png

上圖的第二步判斷操作eventLoop.inEventLoop()這一步實際上判斷的是
this.thread == Thread.currentThread(),而this.thread僅僅只是線上程開啟的時候賦值過一次,所以我上面說,只要執行緒已經開啟,這類註冊任務便可以直接執行,省去了佇列的push和pull的過程。(執行緒的開啟可以參考我的Netty系列(三))。

下面開始說註冊的核心函式register0。


register0 核心函式

  1. doRegister()這個方法在seletor上註冊了NioServerSocketChannel例項,但是沒有繫結任何興趣事件。我們要知道,一個ServerSocketChannel要想接受客戶端的連線必須要繫結一個Accept的興趣事件的,所以這裡的註冊流程還是不完整的,後面肯定有方法將這個坑給填上。註冊程式碼如下:
    selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
  2. 第二步的主要功能是將handler在我們們的pipeline這個管道中串聯起來,方便後面的業務處理流程。按我們們最開始構造的服務端的程式碼,目前pipeline中的handler的順序應該如下圖所示:

在經過pipeline.invokeHandlerAddedIfNeeded()這個方法的呼叫之後,pipeline中的管道應該如下圖所示:

呼叫這個方法的流程一步步點進去不難發現,實際上呼叫的是你重寫handler之後的handlerAdded方法,而ChannelInitializer這個類的handlerAdded方法又呼叫的本身的initChannel方法:

所以接下來就會走這個流程:

這段程式碼是不是很熟悉,我在上面說initAndRegister這個函式的時候也有用到這塊程式碼的。通過上面這段簡單的流程,我們可以對handler是怎樣和pipeline組合到一起有了一個大概的瞭解。

這裡給大家補充個小知識點:

資料從HeadContext進來,按pipeline從前往後找InboundHandler處理業務;但是發出去時,從TailContext開始,按pipeline從後往前找OutboundHandler處理業務,這兩者剛好相反。所以我們們編解碼的handler都要新增在pipeline的最前面,也就是head後面的位置,這樣data進來就可以進行decode,出去就encode。

  1. 前面介紹過只是將channel註冊到selector上面了,但是未繫結興趣事件。這個第三步就是一個填坑的操作,將ServerSocketChannel所需要的興趣事件給補充上了。fireChannelActive方法呼叫的是handler中的channelActive方法。我們們看一下HeadContext中的channelActive方法,註冊興趣事件時在我圈紅的方法裡面完成的:

我們們看下呼叫流程:

順著這條鏈路走下去,最終呼叫到一個doBeginRead的方法,關注兩個點,一個是selectionKey,一個是readInterestOp。前者是在上面的第一步
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this)過程中進行的賦值,後者是在呼叫NioServerSocketChannel的無參建構函式時進行的賦值:

在通過位運算if ((interestOps & readInterestOp) == 0)判斷該興趣事件如果尚未被註冊的話便進行註冊興趣事件。所以這裡會註冊一個OP_ACCEPT興趣事件。

這樣,NioServerSocketChannel的註冊流程就結束了。也能正常接收客戶端發過來的連線請求了。


NioServerSocketChannel註冊總結圖

NioServerSocketChannel.png
NioServerSocketChannel.png

上面是整理的是NioServerSocketChannel從initAndRegister開始的註冊流程圖。


NioSocketChannel建立及註冊

NioSocketChannel的註冊流程我們們從processSelectedKey這個方法開始看,上一篇文章有介紹這個方法裡面實際上是根據不同的興趣事件做不同的處理,這裡我們關注下OP_ACCEPT興趣事件。

我們們完整的看看這個read方法:

 1        public void read() {
2            //判斷NioEventLoop是否已經開啟執行緒了
3            assert eventLoop().inEventLoop();
4            final ChannelConfig config = config();
5            //取出 與NioServerSocketChannel繫結的pipeline
6            final ChannelPipeline pipeline = pipeline();
7            final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
8            allocHandle.reset(config);
9
10            boolean closed = false;
11            Throwable exception = null;
12            try {
13                try {
14                    do {
15                        // 生成NioSocketChannel例項
16                        int localRead = doReadMessages(readBuf);
17                        if (localRead == 0) {
18                            break;
19                        }
20                        if (localRead < 0) {
21                            closed = true;
22                            break;
23                        }
24
25                        allocHandle.incMessagesRead(localRead);
26                    } while (allocHandle.continueReading());
27                } catch (Throwable t) {
28                    exception = t;
29                }
30
31                int size = readBuf.size();
32                for (int i = 0; i < size; i ++) {
33                    readPending = false;
34                    //沿著pipeline從head-->tail呼叫channelRead方法
35                    pipeline.fireChannelRead(readBuf.get(i));
36                }
37                // 清除處理過後的訊息,供下次接收使用
38                readBuf.clear();
39                allocHandle.readComplete();
40                pipeline.fireChannelReadComplete();
41
42                if (exception != null) {
43                    closed = closeOnReadError(exception);
44
45                    pipeline.fireExceptionCaught(exception);
46                }
47
48                if (closed) {
49                    inputShutdown = true;
50                    if (isOpen()) {
51                        close(voidPromise());
52                    }
53                }
54            } finally {
55                if (!readPending && !config.isAutoRead()) {
56                    removeReadOp();
57                }
58            }
59        }
複製程式碼
  1. 看下doReadMessages這個方法:

這一塊就是呼叫serverSocketChannel.accept()獲取SocketChannel,這一塊不清楚的可以瞭解一下JAVA NIO相關的知識點。獲取SocketChannel例項後包裝成為NioSocketChannel例項,然後塞到集合中供後面進行處理。

  1. 看下pipeline.fireChannelRead(readBuf.get(i))這個方法,從第一步中我們們可以知道readBuf.get(i)得到的就是一個NioSocketChannel例項,然後在pipeline中從head-->tail依次執行handler中的channelRead方法。

現在的pipeline如下(不熟悉這張圖的話可以翻到前面去看一下):

實際上主要看ServerBootstrapAcceptor的channelRead方法:

這個流程大家應該不會很陌生了吧!

1.給NioSocketChannel繫結的pipeline上新增handle。
2.進行Option以及Attr的設定。
3.進行register註冊操作。

後續的register操作和NioServerSocketChannel差不了多少,就在註冊興趣事件時一個是註冊的OP_ACCEPT,另一個註冊的是OP_READ。


NioSocketChannel的建立流程圖

NioSocketChannel.png
NioSocketChannel.png

上面是整理的是NioSocketChannel的註冊流程圖,只畫到register處,後續具體的註冊流程可以參考NioServerSocketChannel的註冊流程。


End

相關文章