上一節我們找到了ServerSocketChannel的生成,註冊Selector,綁定端口啓動等等:netty極簡教程(六):Netty是如何屏蔽ServerSocketChannel啓動的,
接下來接續驗證在Netty中Selector的生成使用以及我們jdk 原生工作線程再netty中是怎麼啓動工作的: NioEventLoopGroup
示例源碼: https://github.com/jsbintask22/netty-learning
NioEventLoopGroup
還記得我們前面粗談的boss以及work線程嗎,它是一個線程池,既然它是一個線程池,那它內部肯定有成員變量用來訪問它所持有的線程,就在
NioEventLoopGroup
的構造函數中:從這裏我們就知道這個線程池內部有一個
SelectProvider
,接着繼續往下面走:我們知道這裏的executor肯定是null的,所以它默認構造了一個
ThreadPerTaskExecutor
這裏值得注意的是,它沒有做其他任何操作,直接就是new了一個線程
並且start
了,所以這個使用這個executor提交的任務它直接就是使用線程並且直接啓動了;
接着繼續往下面走,它將children全部實例化並且該children是一個NioEventLoop實例數組(將上面的executor丟了進去);
所以現在關鍵地方在於,這個NioEventLoop是什麼時候往ThreadPerTaskExecutor丟了一個任務,我們繼續追蹤它NioEventLoop
觀察它的構造方法: 這裏指的注意的是,我們在NioEventLoop的構造方法中發現了Selector已經生成,
也就是說:
children數組多大就有多少個線程就有多少個Selector
,到這裏,我們第5節用jdk寫的reactor子線程都對應了一個selector在這裏得以驗證;
我們回到上一節解析的registerAndInit()
方法
最後有一個register的步驟,我們上一節說在這個方法中註冊ServerChannel到Selector,其實除此之外,還有另外的步驟:
由於剛啓動時是在main線程中,所以當前線程不等於boss線程:
public boolean inEventLoop(Thread thread) {
return thread == this.thread;
}
即先走下面的
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
最終使用executor提交開啓了一個新線程(忘記的回憶一下ThreadPerTaskExecutor):
最終調用
NioEventLoop.run()
方法,開啓Selector的無限select操作:select到準備好的事件或者任務隊列中有任務時(我們一開始的Channel註冊就添加到了任務隊列),開始執行
processSelectedKeys
處理事件:和我們寫的原生jdk一樣,開始遍歷selectedKey,並且根據不同的事件類型在
processSelectedKey
中處理:所以,如果我們進入的accept事件,說明channel是ServerSocketChannel,則執行
NioMessageUnsafe.read ()
方法,接着調用NioServerSocketChannel的doReadMessages從而接受一個新連接:
@Override
protected int doReadMessages(List<Object> buf) throws Exception {
SocketChannel ch = SocketUtils.accept(javaChannel());
try {
if (ch != null) {
buf.add(new NioSocketChannel(this, ch));
return 1;
}
} catch (Throwable t) {
logger.warn("Failed to create a new channel from an accepted socket.", t);
try {
ch.close();
} catch (Throwable t2) {
logger.warn("Failed to close a socket.", t2);
}
}
return 0;
}
最後會觸發SererSocketChannel中pipeline中的read方法,此時我們在上一節已經埋好伏筆,就是ServerBootstrapAcceptor
的read會觸發,從而將新接受的連接SocketChannel再次註冊到selector,並且work子線程也會開始無限循環並進行上面的操作:
如圖,上面的
ch.eventLoop().execute
我們已經說過會直接開啓一個新線程並且執行接着再初始化我們的子Socket應該初始化的各種Handler(具體示什麼後面詳解); 這樣,work線程也會就會開始工作。所有對應原生JDK的啓動操作步驟就全部找出;