Netty系列(四):NioServerSocketChannel註冊

前言

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


服務端代碼

上面的代碼段我已經添加了詳細的註釋,具體的註冊流程得從我標紅的bind這個方法開始,咱們隨着這條線追蹤一下,會發現最終會調用到doBind方法,裏面有個initAndRegister函數,從這裏開始就正式進入建立註冊流程了。
java


initAndRegister函數

在這個函數中,咱們主要關注三個點:ios

  1. 調用NioServerSocketChannel的無參構造函數進行其實例的初始化。
  2. 進行Option以及Attr的設置,這個方法裏面,你們能夠關注一下我下面圈紅的ServerBootstrapAcceptor這個類,它會被註冊到NioServerSocketChannel的pipeline中去,後面SocketChannel的註冊流程會依靠這個類作一些操做,你們先留個印象便可,重點
  3. 開啓NioEventLoop的線程,並執行register操做。

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

server_channel_register.png
server_channel_register.png

上圖的第二步判斷操做eventLoop.inEventLoop()這一步實際上判斷的是
this.thread == Thread.currentThread(),而this.thread僅僅只是在線程開啓的時候賦值過一次,因此我上面說,只要線程已經開啓,這類註冊任務即可以直接執行,省去了隊列的push和pull的過程。(線程的開啓能夠參考個人Netty系列(三))。socket

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


register0 核心函數

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

在通過pipeline.invokeHandlerAddedIfNeeded()這個方法的調用以後,pipeline中的管道應該以下圖所示:oop

調用這個方法的流程一步步點進去不難發現,實際上調用的是你重寫handler以後的handlerAdded方法,而ChannelInitializer這個類的handlerAdded方法又調用的自己的initChannel方法:
this

因此接下來就會走這個流程:
spa

這段代碼是否是很熟悉,我在上面說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

相關文章
相關標籤/搜索