本文主要介紹的是服務端NioServerSocketChannel建立和註冊流程以及客戶端鏈接到服務端後的NioSocketChannel的建立和註冊流程,這兩步都是很關鍵的。在介紹的過程當中,中間會穿插着進行ChannelHandler與ChannelPipeline的一些簡單的介紹。html
上面的代碼段我已經添加了詳細的註釋,具體的註冊流程得從我標紅的bind
這個方法開始,咱們隨着這條線追蹤一下,會發現最終會調用到doBind
方法,裏面有個initAndRegister
函數,從這裏開始就正式進入建立註冊流程了。
java
在這個函數中,咱們主要關注三個點:ios
咱們直接從第三步開始說,涉及到前面的知識點我後面會一一解釋。app
上圖的第二步判斷操做eventLoop.inEventLoop()
這一步實際上判斷的是this.thread == Thread.currentThread()
,而this.thread
僅僅只是在線程開啓的時候賦值過一次,因此我上面說,只要線程已經開啓,這類註冊任務即可以直接執行,省去了隊列的push和pull的過程。(線程的開啓能夠參考個人Netty系列(三))。socket
下面開始說註冊的核心函數register0。函數
doRegister()
這個方法在seletor上註冊了NioServerSocketChannel實例,可是沒有綁定任何興趣事件。咱們要知道,一個ServerSocketChannel要想接受客戶端的鏈接必需要綁定一個Accept的興趣事件的,因此這裏的註冊流程仍是不完整的,後面確定有方法將這個坑給填上。註冊代碼以下:selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
在通過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。
fireChannelActive
方法調用的是handler中的channelActive
方法。我們看一下HeadContext中的channelActive
方法,註冊興趣事件時在我圈紅的方法裏面完成的:我們看下調用流程:
順着這條鏈路走下去,最終調用到一個doBeginRead
的方法,關注兩個點,一個是selectionKey
,一個是readInterestOp
。前者是在上面的第一步selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this)
過程當中進行的賦值,後者是在調用NioServerSocketChannel的無參構造函數時進行的賦值:
在經過位運算if ((interestOps & readInterestOp) == 0)
判斷該興趣事件若是還沒有被註冊的話便進行註冊興趣事件。因此這裏會註冊一個OP_ACCEPT興趣事件。
這樣,NioServerSocketChannel的註冊流程就結束了。也能正常接收客戶端發過來的鏈接請求了。
上面是整理的是NioServerSocketChannel從initAndRegister
開始的註冊流程圖。
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 }
複製代碼
doReadMessages
這個方法:這一塊就是調用serverSocketChannel.accept()
獲取SocketChannel,這一塊不清楚的能夠了解一下JAVA NIO相關的知識點。獲取SocketChannel實例後包裝成爲NioSocketChannel實例,而後塞到集合中供後面進行處理。
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的註冊流程圖,只畫到register處,後續具體的註冊流程能夠參考NioServerSocketChannel的註冊流程。