1-netty源碼分析之Server
- 看netty源碼之后進行總結的第一篇筆記,無非幫助自己對于看代碼的一個總結,方便自己回顧學習;依然保持從demo出發,服務端、客戶端、線程模型、管道四篇核心點記錄;
一.demo出發,啟動server。
public final class EchoServer {
static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));
public static void main(String[] args) throws Exception {
// Configure the server.
EventLoopGroup bossGroup = new NioEventLoopGroup(3);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
if (sslCtx != null) {
p.addLast(sslCtx.newHandler(ch.alloc()));
}
p.addLast(new EchoServerHandler());
}
});
// Start the server.
ChannelFuture f = b.bind(PORT).sync();
// Wait until the server socket is closed.
f.channel().closeFuture().sync();
} finally {
// Shut down all event loops to terminate all threads.
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
以上就是netty啟動server的經典demo了,截自源碼;
1.EventLoopGroup:netty框架中的reactor線程模型中"線程"就是由它提供,這個也是netty框架的核心概念之一,一個group可以包含多個EventLoop(即多個線程)。
2.ServerBootstrap:netty服務端啟動引擎,也可以認為是netty啟動的輔助類,以build的方式組裝netty的相關配置及組件,最終串氣各個組件啟動服務;
3.NioServerSocketChannel:服務端持有的channel,字面翻譯是“渠道”,那么它代表一個具體的與客戶端的連接,或者與IO相關的操作,它會和一個特定的EventLoop(即線程)綁定用以處理相關的IO操作;
4.ChannelOption:socket相關參數,標識當服務器請求處理線程全滿時,用于臨時存放已完成三次握手的請求的隊列的最大長度
5.Handler:處理器,處理的就是整個通信過程中的相關事件或者數據,我們擴展自己的handler就可以實現通信數據的業務處理了;
6.ChannelInitializer:一種ChannelInboundHandler,看initChannel方法里,提供了ChannelPipeline用以組裝各種handler,那么ChannelInitializer就是組裝相關handler的作用;
7.ChannelPipeline:理解為管道,那么管道里鏈路管理著各種handler,數據經過管道流向各種handler節點,處理后流出或者流入;
8.ChannelFuture:jdk多線程的Future相似,異步處理的回調監聽結果,即在整個netty框架的異步操作成功或者失敗都會觸發監聽即可得到相關結果;
用一張圖將以上組件進行串起來:
啟動組件大致概念簡單介紹如此,后面詳細概念繼續以debug模式講解
1.EventLoopGroup初始化
EventLoopGroup bossGroup = new NioEventLoopGroup(3);
EventLoopGroup workerGroup = new NioEventLoopGroup();
看一下類圖:
前面講過,EventLoopGroup是netty線程模型中的線程部分,那么這里是怎么體現的呢?其實這里的父類MultithreadEventExecutorGroup里面封裝了一個EventExecutor[] 數組,而EventExecutor是一個接口,找到最下層的實現結構如下:
public abstract class MultithreadEventExecutorGroup extends AbstractEventExecutorGroup {
private final EventExecutor[] children;
...
}
這里可以想下NioEventLoop就是一個具體的“線程”了,為啥?依照上圖找到父類SingleThreadEventExecutor,可以看到里面包裝了一個成員變量Thread,有次可知:SingleThreadEventExecutor即是一個線程的抽象,因而NioEventLoop可以理解為一個線程了,相關的執行操作都是委托到里面的thread去執行。只不過NioEventLoop有更多的大于線程的能力,比如schedule等,這里代碼繼承關系可以提現。
public abstract class SingleThreadEventExecutor extends AbstractScheduledEventExecutor implements OrderedEventExecutor {
private final Thread thread;
...
}
知道了 EventLoopGroup 是一個線程數組,那么就回到初始化的地方,跟著代碼debug進入看看初始化的具體干了什么。
public NioEventLoopGroup(int nThreads, ThreadFactory threadFactory) {
this(nThreads, threadFactory, SelectorProvider.provider());
}
public NioEventLoopGroup(int nThreads, ThreadFactory threadFactory, final SelectorProvider selectorProvider) {
this(nThreads, threadFactory, selectorProvider, DefaultSelectStrategyFactory.INSTANCE);
}
public NioEventLoopGroup(int nThreads, ThreadFactory threadFactory, final SelectorProvider selectorProvider, final SelectStrategyFactory selectStrategyFactory) {
/** reject:設置任務隊列線程池拒絕策略,默認直接拋異常 */
super(nThreads, threadFactory, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject());
}
此處連續三次調用可以看出做了一些額外操作,分別是
- 1.獲取SelectorProvider
- 2.獲取SelectStrategyFactory
- 3.有關任務隊列的拒絕策略:RejectedExecutionHandlers.reject()
繼續往下:
根據debug可以看出,這里有個線程數的默認值,根據當前處理器核心數 * 2計算得出,當然我們傳的是3,因此直接使用3創建一個new SingleThreadEventExecutor[nThreads]數組;核心初始化線程如下:
protected MultithreadEventExecutorGroup(int nThreads, ThreadFactory threadFactory, Object... args) {
...
/** 創建一個大小為 nThreads 的 SingleThreadEventExecutor 數組 */
children = new SingleThreadEventExecutor[nThreads];
/**
* 根據 nThreads 的大小, 創建不同的 Chooser,
* 即如果 nThreads 是 2 的冪, 則使用 PowerOfTwoEventExecutorChooser, 反之使用 GenericEventExecutorChooser.
* 不論使用哪個 Chooser, 它們的功能都是一樣的, 即從 children 數組中選出一個合適的 EventExecutor 實例.
*/
if (isPowerOfTwo(children.length)) {
chooser = new PowerOfTwoEventExecutorChooser();
} else {
chooser = new GenericEventExecutorChooser();
}
for (int i = 0; i < nThreads; i ++) {
boolean success = false;
try {
/**
* 調用 newChhild 方法初始化 children 數組.
* 具體子類實現
*/
children[i] = newChild(threadFactory, args);
success = true;
} catch (Exception e) {
...
} finally {
if (!success) {
...
}
}
}
}
主要干了這兩件事:
- 1.根據線程數創建一個ExecutorChooser,其實這里也是體現netty對于性能的追求
- 2.調用模板方法創建具體的 線程
進入子類具體的方法:NioEventLoopGroup
NioEventLoop(NioEventLoopGroup parent, ThreadFactory threadFactory, SelectorProvider selectorProvider, SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler) {
super(parent, threadFactory, false, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler);
if (selectorProvider == null) {
throw new NullPointerException("selectorProvider");
}
if (strategy == null) {
throw new NullPointerException("selectStrategy");
}
provider = selectorProvider;
final SelectorTuple selectorTuple = openSelector();
selector = selectorTuple.selector;
unwrappedSelector = selectorTuple.unwrappedSelector;
selectStrategy = strategy;
}
一連串的super調用之后進入核心線程創建方法:
protected SingleThreadEventExecutor(EventExecutorGroup parent, ThreadFactory threadFactory, boolean addTaskWakesUp, int maxPendingTasks, RejectedExecutionHandler rejectedHandler) {
if (threadFactory == null) {
throw new NullPointerException("threadFactory");
}
this.parent = parent;
this.addTaskWakesUp = addTaskWakesUp;
thread = threadFactory.newThread(new Runnable() {
@Override
public void run() {
boolean success = false;
updateLastExecutionTime();
try {
/** 多態,調用NioEventLoop的run方法 */
SingleThreadEventExecutor.this.run();
success = true;
} catch (Throwable t) {
logger.warn("Unexpected exception from an event executor: ", t);
} finally {
...
}
}
});
...
}
可以看到這里初始化了具體的線程,并且指定了run方法的調用執行為:
SingleThreadEventExecutor.this.run();
那這里的run方法就是EventLoop的核心了,干了什么后面詳細說明,先看下run方法干了什么:
/**
* Netty 的事件循環機制
* 當 taskQueue 中沒有任務時, 那么 Netty 可以阻塞地等待 IO 就緒事件;
* 而當 taskQueue 中有任務時, 我們自然地希望所提交的任務可以盡快地執行, 因此 Netty 會調用非阻塞的 selectNow() 方法, 以保證 taskQueue 中的任務盡快可以執行.
*
* 1.輪詢IO事件
* 2.處理輪詢到的事件
* 3.執行任務隊列中的任務
* */
@Override
protected void run() {
for (;;) {
try {
/** 如果任務隊列沒有任務,則進行一次selectNow() */
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.SELECT:
/**
* 輪詢出注冊在selector上面的IO事件
*
* wakenUp 表示是否應該喚醒正在阻塞的select操作,
* 可以看到netty在進行一次新的loop之前,都會將wakeUp 被設置成false,標志新的一輪loop的開始
*/
select(wakenUp.getAndSet(false));
if (wakenUp.get()) {
selector.wakeup();
}
// fall through
default:
}
cancelledKeys = 0;
/**
* 第一步是通過 select/selectNow 調用查詢當前是否有就緒的 IO 事件. 那么當有 IO 事件就緒時, 第二步自然就是處理這些 IO 事件啦.
*/
needsToSelectAgain = false;
/**
* 此線程分配給 IO 操作所占的時間比
* 即運行 processSelectedKeys 耗時在整個循環中所占用的時間
*/
final int ioRatio = this.ioRatio;
/** 當 ioRatio 為 100 時, Netty 就不考慮 IO 耗時的占比, 而是分別調用 processSelectedKeys()、runAllTasks(); */
if (ioRatio == 100) {
try {
/** 查詢就緒的 IO 事件后 進行處理 */
processSelectedKeys();
} finally {
// Ensure we always run tasks.
/** 運行 taskQueue 中的任務. */
runAllTasks();
}
}
/**
* ioRatio 不為 100時, 則執行到 else 分支, 在這個分支中, 首先記錄下 processSelectedKeys() 所執行的時間(即 IO 操作的耗時),
* 然后根據公式, 計算出執行 task 所占用的時間, 然后以此為參數, 調用 runAllTasks().
*/
else {
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys();
} finally {
// Ensure we always run tasks.
final long ioTime = System.nanoTime() - ioStartTime;
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
}
} catch (Throwable t) {
handleLoopException(t);
}
// Always handle shutdown even if the loop processing threw an exception.
try {
...
}
}
哈哈,無限死循環,無非就是輪訓,那么netty的reactor線程模型就此方法為核心點跟進即可;
EventLoopGroup初始化先講到這里,線程模型后面再詳細筆記。
二.ServerBootstrap組裝組件
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
if (sslCtx != null) {
p.addLast(sslCtx.newHandler(ch.alloc()));
}
p.addLast(new EchoServerHandler());
}
});
-
1.組裝兩個EventGroup:bossGroup, workerGroup;bossGroup用來專門負責綁定到端口監聽連接事件,workerGroup用來處理每個接收到的連接
image.png
可以看到bossGroup只是簡單的賦值給父類的成員變量,workerGroup賦值給ServerBootstrap的ServerBootstrap的childGroup屬性,既簡單的賦值而已;
-
2.組裝channel
image.png
這里不是將傳入的NioServerSocketChannel作為類型構造一個工廠類賦值給自己的channelFactory,后續在啟動初始化時利用其構造channel;
- 3.option、handler、childHandler只是簡單的賦值而已,不多解釋;ChannelInitializer在前面講過,用以封裝業務handler鏈,后面啟動時會講解。
三.啟動服務
ChannelFuture f = b.bind(PORT).sync();
可以說啟動的核心邏輯就在這里,確實做了不少事情,可以想象nio多路復用、事件輪訓、selector與channel的注冊、channel與pipeline的綁定、channel與eventLoop的綁定等等都在這一步做的,那么詳細debug走起。
進入核心方法doBind;
- initAndRegister
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
/** 初始化channel --> client 獲取 NioSocketChannel; --> server 獲取 ServerSocketChannel*/
channel = channelFactory().newChannel();
init(channel);
} catch (Throwable t) {
...
}
/**
* Channel 注冊過程:
* 1.將 Channel 與對應的 EventLoop 關聯,
* 因此這也體現了, 在 Netty 中, 每個 Channel 都會關聯一個特定的 EventLoop, 并且這個 Channel 中的所有 IO 操作都是在這個 EventLoop 中執行的;
*
* 2.當關聯好 Channel 和 EventLoop 后, 會繼續調用底層的 Java NIO SocketChannel 的 register 方法, 將底層的 Java NIO SocketChannel 注冊到指定的 selector 中.
* 通過這兩步, 就完成了 Netty Channel 的注冊過程.
*
* 3.若是服務端注冊,則group()返回的是bossGroup
*/
ChannelFuture regFuture = group().register(channel);
return regFuture;
}
做了三件事:
- 1.根據之前構造的工廠new一個channel
- 2.將創建的channel進行一系列初始化動作
- 3.將channel與對應的EventLoopGroup關聯,即channel綁定到指定的線程;
一個一個進行分解:
-
1.new channel
image.png
利用構造器進行instance那么找到構造器:
1.構造器中直接調用SelectorProvider打開一個ServerSocketChannel,可以看到這一步到了與nio交互了;
2.直接super父類構造中,不斷super就會到AbstractChannel中核心點
看上面三行:
1.將channel進行賦值,此時為空
2.構造一個UnSafe對象,這里netty真正的讀寫等IO事件都是交給UnSafe去操作的,這里返回的是一個NioMessageUnsafe,服務端需要的UnSafe對象,將新的連接注冊到worker線程組【netty將一個新連接的建立也當作一個io操作來處理,這里的Message的含義我們可以當作是一個SelectableChannel,讀的意思就是accept一個SelectableChannel,寫的意思是針對一些無連接的協議,比如UDP來操作的】
-
3.創建一個數據自己的piepline,用以后續組裝handler
protected DefaultChannelPipeline(Channel channel) { this.channel = ObjectUtil.checkNotNull(channel, "channel"); /** 維護了一個以 AbstractChannelHandlerContext 為節點的雙向鏈表 */ tail = new TailContext(this); head = new HeadContext(this); head.next = tail; tail.prev = head; }
這里是上面構造piepline的方法,可以看到這里的鏈表結構,首先將channel綁定自己,然后構造head,tail收尾節點,同時這里對對于head,tail類型是ChannelHandlerContext,理解為一個handler的context,可以做一些額外的動作。
可以看到這里有個重要的屬性inbound,這個是干啥的呢?
ChannelHandler有兩個子類ChannelInboundHandler和ChannelOutboundHandler,這兩個類對應了兩個數據流向,如果數據是從外部流入我們的應用程序,我們就看做是inbound,相反便是outbound,因此這個代表handler的流向節點意思了; 同時head和tail都要與具體的UnSafe綁定,因為這里是數據流向的首尾節點,那么自然就是具體的非讀即寫數據了,此處自然交給綁定的UnSafe去操作,因此這就是綁定UnSafe的理由了。
OK,new channel介紹到這里,繼續回到initAndRegister中的init方法:
-
2.init(channel)
@Override void init(Channel channel) throws Exception { ... ChannelPipeline p = channel.pipeline(); ... /** * 加入新連接處理器,用來專門接受新連接 * 初始化channel時,加入匿名ChannelHandler, 作用就是在register channel到selector時回調init方法,將boss上的handler加入pipeline中,并且ServerBootstrapAcceptor handler用以綁定childGroup和NioSocketChannel */ p.addLast(new ChannelInitializer<Channel>() { @Override public void initChannel(final Channel ch) throws Exception { final ChannelPipeline pipeline = ch.pipeline(); /** 這里的handler返回的是主.handler(new LoggingHandler(LogLevel.INFO)) 中的handler*/ ChannelHandler handler = handler(); if (handler != null) { pipeline.addLast(handler); } ch.eventLoop().execute(new Runnable() { @Override public void run() { /** 這里的 childGroup.register 就是將 workerGroup 中的某個 EventLoop 和 NioSocketChannel 關聯 */ pipeline.addLast(new ServerBootstrapAcceptor(ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)); } }); } }); }
省略非主要代碼,這里拿到構造的pipeline,然后加入一個ChannelInitializer的handler,并且植入一個initChannel方法,說下initChannel的作用【該方法當然此時不會執行】:
1.獲取主handler并加入piepline
2.在taskQueu中offer一個任務,具體執行的是將ServerBootstrapAcceptor加入到pieline中 ,而ServerBootstrapAcceptor主要作用是將workerGroup 中的某個 EventLoop 和 NioSocketChannel 關聯,具體如何觸發在后面client發起連接時會細說。
initChannel做的事不多,繼續回到initAndRegister方法
-
3.group().register(channel)
/** * Channel 注冊過程: * 1.將 Channel 與對應的 EventLoop 關聯, * 因此這也體現了, 在 Netty 中, 每個 Channel 都會關聯一個特定的 EventLoop, 并且這個 Channel 中的所有 IO 操作都是在這個 EventLoop 中執行的; * * 2.當關聯好 Channel 和 EventLoop 后, 會繼續調用底層的 Java NIO SocketChannel 的 register 方法, 將底層的 Java NIO SocketChannel 注冊到指定的 selector 中. * 通過這兩步, 就完成了 Netty Channel 的注冊過程. * * 3.若是服務端注冊,則group()返回的是bossGroup */ ChannelFuture regFuture = group().register(channel);
其實前面講過,每個channel都會綁定一個EventLoop用以專門處理跟此Channel相關的IO事件,看代碼跟蹤,這里先記一下bossGroup對象:
image.png
可以很明顯的跟蹤到這里group()返回的就是bossGroup,繼續往下走:MultithreadEventLoopGroup的register方法體:首先解剖下next()做了什么:
@Override
public ChannelFuture register(Channel channel) {
return next().register(channel);
}
@Override
public EventLoop next() {
/** 獲取一個可用的 SingleThreadEventLoop */
return (EventLoop) super.next();
}
這里看到了chooser,也就是EventExecutorChooser,記得我們當初設置的是3,非2的冪,初始化EventLopp的時候,因此取得GenericEventExecutorChooser類型的選擇器:
if (isPowerOfTwo(children.length)) {
chooser = new PowerOfTwoEventExecutorChooser();
} else {
chooser = new GenericEventExecutorChooser();
}
private final class GenericEventExecutorChooser implements EventExecutorChooser {
@Override
public EventExecutor next() {
return children[Math.abs(childIndex.getAndIncrement() % children.length)];
}
}
看邏輯abs算法出一個主線程,用以實際的操作,其實這里有個很重要的要點明:
主線程無論初始化多少,最終執行操作的永遠只有一個線程,因此這里在初始化EventLoopGroup bossGroup = new NioEventLoopGroup(3);時直接將參數設為1即可
繼續回到register:
生成DefaultChannelPromise后,調用UnSafe對象進行 register進行具體的底層相關的注冊操作,這就實現了注釋中的第二部:
2.當關聯好 Channel 和 EventLoop 后, 會繼續調用底層的 Java NIO SocketChannel 的 register 方法, 將底層的 Java NIO SocketChannel 注冊到指定的 selector 中.通過這兩步, 就完成了 Netty Channel 的注冊過程.
這里由于是用戶線程,因此轉成EventLoop的任務扔進隊列里等待去執行,于是會有異步回調就靠這個ChannelPromise了。什么時候會執行任務,那就要先啟動線程:
繼續跟蹤代碼:
具體做了:
1.判斷是不是EventLoop線程,如果是直接offer任務;
2.如果使用戶線程,先啟動線程EventLoop線程,再offer任務
那么很明顯清楚,這里對任務進行了歸一處理,猜想啟動了EventLoop線程后,大概就是輪訓task隊列的任務了,那么繼續跟蹤啟動的邏輯:
這里的thread到底是什么,回到EventLoop初始化的地方:
protected SingleThreadEventExecutor(EventExecutorGroup parent, ThreadFactory threadFactory, boolean addTaskWakesUp, int maxPendingTasks, RejectedExecutionHandler rejectedHandler) {
thread = threadFactory.newThread(new Runnable() {
@Override
public void run() {
try {
/** 多態,調用NioEventLoop的run方法 */
SingleThreadEventExecutor.this.run();
success = true;
} catch (Throwable t) {
logger.warn("Unexpected exception from an event executor: ", t);
} finally {
}
}
});
}
標注核心邏輯,這里的run就是當初的那個NioEventLoop的具體實現的run,防止走丟,再貼一次代碼:
/**
* Netty 的事件循環機制
* 當 taskQueue 中沒有任務時, 那么 Netty 可以阻塞地等待 IO 就緒事件;
* 而當 taskQueue 中有任務時, 我們自然地希望所提交的任務可以盡快地執行, 因此 Netty 會調用非阻塞的 selectNow() 方法, 以保證 taskQueue 中的任務盡快可以執行.
*
* 1.輪詢IO事件
* 2.處理輪詢到的事件
* 3.執行任務隊列中的任務
* */
@Override
protected void run() {
for (;;) {
try {
/** 如果任務隊列沒有任務,則進行一次selectNow() */
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.SELECT:
/**
* 輪詢出注冊在selector上面的IO事件
*
* wakenUp 表示是否應該喚醒正在阻塞的select操作,
* 可以看到netty在進行一次新的loop之前,都會將wakeUp 被設置成false,標志新的一輪loop的開始
*/
select(wakenUp.getAndSet(false));
if (wakenUp.get()) {
selector.wakeup();
}
// fall through
default:
}
cancelledKeys = 0;
/**
* 第一步是通過 select/selectNow 調用查詢當前是否有就緒的 IO 事件. 那么當有 IO 事件就緒時, 第二步自然就是處理這些 IO 事件啦.
*/
needsToSelectAgain = false;
/**
* 此線程分配給 IO 操作所占的時間比
* 即運行 processSelectedKeys 耗時在整個循環中所占用的時間
*/
final int ioRatio = this.ioRatio;
/** 當 ioRatio 為 100 時, Netty 就不考慮 IO 耗時的占比, 而是分別調用 processSelectedKeys()、runAllTasks(); */
if (ioRatio == 100) {
try {
/** 查詢就緒的 IO 事件后 進行處理 */
processSelectedKeys();
} finally {
// Ensure we always run tasks.
/** 運行 taskQueue 中的任務. */
runAllTasks();
}
}
/**
* ioRatio 不為 100時, 則執行到 else 分支, 在這個分支中, 首先記錄下 processSelectedKeys() 所執行的時間(即 IO 操作的耗時),
* 然后根據公式, 計算出執行 task 所占用的時間, 然后以此為參數, 調用 runAllTasks().
*/
else {
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys();
} finally {
// Ensure we always run tasks.
final long ioTime = System.nanoTime() - ioStartTime;
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
}
} catch (Throwable t) {
handleLoopException(t);
}
// Always handle shutdown even if the loop processing threw an exception.
try {
...
} catch (Throwable t) {
handleLoopException(t);
}
}
}
看到輪訓了,那么EventLoop線程啟動了,很自然就能找到當時人進去的任務,其中有個register0的邏輯,線程啟動了,自認就該執行任務了,回到注冊邏輯:
出現了selectionKey,這一步就將 Channel 對應的 Java NIO SockerChannel 注冊到一個 eventLoop 的 Selector 中, 并且將當前 Channel 作為 attachment.回到register0
pipeline.invokeHandlerAddedIfNeeded()
這句是干嘛的呢? 還記得初始化channel--> init(channel)的方法嗎,里面有個動作是這樣的:
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(final Channel ch) throws Exception {
final ChannelPipeline pipeline = ch.pipeline();
/** 這里的handler返回的是主.handler(new LoggingHandler(LogLevel.INFO)) 中的handler*/
ChannelHandler handler = handler();
if (handler != null) {
pipeline.addLast(handler);
}
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
/** 這里的 childGroup.register 就是將 workerGroup 中的某個 EventLoop 和 NioSocketChannel 關聯 */
pipeline.addLast(new ServerBootstrapAcceptor(ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
這里的invokeHandlerAddedIfNeeded就會觸發這個ChannelInitializer中的initChannel方法,然后順其自然的將ChannelHandler加進piepline,緊接著觸發ServerBootstrapAcceptor將 workerGroup 中的某個 EventLoop 和 NioSocketChannel 關聯;很隨意的將整個線程run起來。
跟蹤一下:
執行效果確實如此。
回過頭看下ChildHandler是不是跟這個時候的一樣,比較一下:
經對比,確實如此,可以說這里就是將childHandler加入piepline的地方了。
還記的我們在初始化ServerBootstrap時有b.group(bossGroup, workerGroup)這樣一個組裝線程的地方,但是會發現整個服務端啟動過程都不會涉及workerGroup相關的啟動,其實這里也是關鍵,這里先將workGroup賦值給ServerBootstrap#ServerBootstrapAcceptor的屬性,在客戶端發起請求時觸發channelRead方法,緊接著就啟動了workGroup,進而輪訓處理相關的IO事件啦,看下代碼:
/**
* 這里講workerGroup綁定到channel,那么這里如何被觸發呢?
* 其實當一個 client 連接到 server 時, Java 底層的 NIO ServerSocketChannel 會有一個 SelectionKey.OP_ACCEPT 就緒事件, 接著就會調用到 NioServerSocketChannel.doReadMessages
*
* 新建此連接的 NioSocketChannel 并添加 childHandler 到 NioSocketChannel 對應的 pipeline 中, 并將此 channel 綁定到 workerGroup 中的某個 eventLoop 中
*/
@Override
@SuppressWarnings("unchecked")
public void channelRead(ChannelHandlerContext ctx, Object msg) {
/** 將msg轉換成對應的channel */
final Channel child = (Channel) msg;
/** 添加用戶自定義的childHandler */
child.pipeline().addLast(childHandler);
/** 設置 NioSocketChannel 對應的 attr和option */
setChannelOptions(child, childOptions, logger);
for (Entry<AttributeKey<?>, Object> e: childAttrs) {
child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
}
try {
/** 將 workerGroup 中的某個 EventLoop 和 NioSocketChannel 關聯 */
childGroup.register(child).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
forceClose(child, future.cause());
}
}
});
} catch (Throwable t) {
forceClose(child, t);
}
}
register就跟bossGroup類似,調用UnSafe去完成具體的操作,中間又把相關的用戶線程收冗成EventLoop進行具體任務操作,完成一系列動作。
OK,大致注冊先介紹到這里
到此為止,整個channel注冊過程完成,繼續回到起點
- 4.doBind
經過piepline事件的傳遞,走到AbstractChannel#AbstractUnsafe的bind方法:
到這里,調用底層的socketChannel進行具體adress綁定工作,整個bind結束。接下來就會調用pipeline.fireChannelActive();進行下達工作了。
下面進行個總結
- 1.初始化相關的組件
- 2.設置好EventLoop,包括bossGroup及workGroup
- 3.初始化channel,并且綁定EventLoop
- 4.啟動線程,將用戶線程進行的動作(比如注冊、綁定等)進行任務化,交給EventLoop處理
- 5.BossEventLoop輪訓事件,接受客戶端請求,觸發WorkerEventLoop啟動處理IO讀寫等操作
- 6.進行任務處理,主要包括將 Channel 對應的 Java NIO SockerChannel 注冊到一個 eventLoop 的 Selector 中,并綁定到具體的地址進行監聽。