前言
本文主要介紹的是服務端NioServerSocketChannel創(chuàng)建和注冊流程以及客戶端連接到服務端后的NioSocketChannel的創(chuàng)建和注冊流程馅闽,這兩步都是很關鍵的歌亲。在介紹的過程中鄙信,中間會穿插著進行ChannelHandler與ChannelPipeline的一些簡單的介紹昆著。
服務端代碼
上面的代碼段我已經添加了詳細的注釋狐榔,具體的注冊流程得從我標紅的bind
這個方法開始暂筝,我們隨著這條線追蹤一下拂共,會發(fā)現(xiàn)最終會調用到doBind
方法,里面有個initAndRegister
函數(shù)料饥,從這里開始就正式進入創(chuàng)建注冊流程了蒲犬。
initAndRegister函數(shù)
在這個函數(shù)中,我們主要關注三個點:
- 調用NioServerSocketChannel的無參構造函數(shù)進行其實例的初始化岸啡。
- 進行Option以及Attr的設置原叮,這個方法里面,大家可以關注一下我下面圈紅的ServerBootstrapAcceptor這個類巡蘸,它會被注冊到NioServerSocketChannel的pipeline中去奋隶,后面SocketChannel的注冊流程會依靠這個類做一些操作,大家先留個印象即可赡若,重點达布。
- 開啟NioEventLoop的線程,并執(zhí)行register操作逾冬。
我們直接從第三步開始說黍聂,涉及到前面的知識點我后面會一一解釋。
上圖的第二步判斷操作eventLoop.inEventLoop()
這一步實際上判斷的是
this.thread == Thread.currentThread()
身腻,而this.thread
僅僅只是在線程開啟的時候賦值過一次产还,所以我上面說,只要線程已經開啟嘀趟,這類注冊任務便可以直接執(zhí)行脐区,省去了隊列的push和pull的過程。(線程的開啟可以參考我的Netty系列(三))她按。
下面開始說注冊的核心函數(shù)register0牛隅。
register0 核心函數(shù)
-
doRegister()
這個方法在seletor上注冊了NioServerSocketChannel實例,但是沒有綁定任何興趣事件酌泰。我們要知道媒佣,一個ServerSocketChannel要想接受客戶端的連接必須要綁定一個Accept的興趣事件的,所以這里的注冊流程還是不完整的陵刹,后面肯定有方法將這個坑給填上默伍。注冊代碼如下:
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
- 第二步的主要功能是將handler在咱們的pipeline這個管道中串聯(lián)起來衰琐,方便后面的業(yè)務處理流程也糊。按咱們最開始構造的服務端的代碼,目前pipeline中的handler的順序應該如下圖所示:
在經過pipeline.invokeHandlerAddedIfNeeded()
這個方法的調用之后羡宙,pipeline中的管道應該如下圖所示:
調用這個方法的流程一步步點進去不難發(fā)現(xiàn)狸剃,實際上調用的是你重寫handler之后的handlerAdded
方法,而ChannelInitializer這個類的handlerAdded
方法又調用的本身的initChannel
方法:
所以接下來就會走這個流程:
這段代碼是不是很熟悉狗热,我在上面說initAndRegister
這個函數(shù)的時候也有用到這塊代碼的捕捂。通過上面這段簡單的流程瑟枫,我們可以對handler是怎樣和pipeline組合到一起有了一個大概的了解。
這里給大家補充個小知識點:
數(shù)據從HeadContext進來指攒,按pipeline從前往后找InboundHandler處理業(yè)務慷妙;但是發(fā)出去時,從TailContext開始允悦,按pipeline從后往前找OutboundHandler處理業(yè)務,這兩者剛好相反膝擂。所以咱們編解碼的handler都要添加在pipeline的最前面,也就是head后面的位置隙弛,這樣data進來就可以進行decode架馋,出去就encode。
- 前面介紹過只是將channel注冊到selector上面了全闷,但是未綁定興趣事件叉寂。這個第三步就是一個填坑的操作,將ServerSocketChannel所需要的興趣事件給補充上了总珠。
fireChannelActive
方法調用的是handler中的channelActive
方法屏鳍。咱們看一下HeadContext中的channelActive
方法,注冊興趣事件時在我圈紅的方法里面完成的:
咱們看下調用流程:
順著這條鏈路走下去局服,最終調用到一個doBeginRead
的方法钓瞭,關注兩個點,一個是selectionKey
,一個是readInterestOp
淫奔。前者是在上面的第一步
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this)
過程中進行的賦值山涡,后者是在調用NioServerSocketChannel的無參構造函數(shù)時進行的賦值:
在通過位運算if ((interestOps & readInterestOp) == 0)
判斷該興趣事件如果尚未被注冊的話便進行注冊興趣事件。所以這里會注冊一個OP_ACCEPT興趣事件唆迁。
這樣鸭丛,NioServerSocketChannel的注冊流程就結束了。也能正常接收客戶端發(fā)過來的連接請求了唐责。
NioServerSocketChannel注冊總結圖
上面是整理的是NioServerSocketChannel從initAndRegister
開始的注冊流程圖系吩。
NioSocketChannel創(chuàng)建及注冊
NioSocketChannel的注冊流程咱們從processSelectedKey
這個方法開始看,上一篇文章有介紹這個方法里面實際上是根據不同的興趣事件做不同的處理妒蔚,這里我們關注下OP_ACCEPT興趣事件。
咱們完整的看看這個read方法:
public void read() {
//判斷NioEventLoop是否已經開啟線程了
assert eventLoop().inEventLoop();
final ChannelConfig config = config();
//取出 與NioServerSocketChannel綁定的pipeline
final ChannelPipeline pipeline = pipeline();
final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
allocHandle.reset(config);
boolean closed = false;
Throwable exception = null;
try {
try {
do {
// 生成NioSocketChannel實例
int localRead = doReadMessages(readBuf);
if (localRead == 0) {
break;
}
if (localRead < 0) {
closed = true;
break;
}
allocHandle.incMessagesRead(localRead);
} while (allocHandle.continueReading());
} catch (Throwable t) {
exception = t;
}
int size = readBuf.size();
for (int i = 0; i < size; i ++) {
readPending = false;
//沿著pipeline從head-->tail調用channelRead方法
pipeline.fireChannelRead(readBuf.get(i));
}
// 清除處理過后的消息月弛,供下次接收使用
readBuf.clear();
allocHandle.readComplete();
pipeline.fireChannelReadComplete();
if (exception != null) {
closed = closeOnReadError(exception);
pipeline.fireExceptionCaught(exception);
}
if (closed) {
inputShutdown = true;
if (isOpen()) {
close(voidPromise());
}
}
} finally {
if (!readPending && !config.isAutoRead()) {
removeReadOp();
}
}
}
- 看下
doReadMessages
這個方法:
這一塊就是調用serverSocketChannel.accept()
獲取SocketChannel肴盏,這一塊不清楚的可以了解一下JAVA NIO相關的知識點。獲取SocketChannel實例后包裝成為NioSocketChannel實例帽衙,然后塞到集合中供后面進行處理菜皂。
- 看下
pipeline.fireChannelRead(readBuf.get(i))
這個方法,從第一步中咱們可以知道readBuf.get(i)
得到的就是一個NioSocketChannel實例厉萝,然后在pipeline中從head-->tail依次執(zhí)行handler中的channelRead方法恍飘。
現(xiàn)在的pipeline如下(不熟悉這張圖的話可以翻到前面去看一下):
實際上主要看ServerBootstrapAcceptor的channelRead方法:
這個流程大家應該不會很陌生了吧榨崩!
1.給NioSocketChannel綁定的pipeline上添加handle。
2.進行Option以及Attr的設置章母。
3.進行register注冊操作母蛛。
后續(xù)的register操作和NioServerSocketChannel差不了多少,就在注冊興趣事件時一個是注冊的OP_ACCEPT乳怎,另一個注冊的是OP_READ彩郊。
NioSocketChannel的創(chuàng)建流程圖
上面是整理的是NioSocketChannel的注冊流程圖,只畫到register處,后續(xù)具體的注冊流程可以參考NioServerSocketChannel的注冊流程蚪缀。