接下來,我們看到的就是兩個非常重要的方法
就是 processSelectedKeys() 和 runAllTasks() 方法了。
selectionKey中ready的事件,如accept、connect、read、write等,由processSelectedKeys方法觸發。屬於I/O任務。
新增到taskQueue中的任務,如register0、bind0等任務,由runAllTasks方法觸發。屬於非I/O任務。
兩種任務的執行時間比由變數ioRatio控制,預設為50,則表示允許非IO任務執行的時間與IO任務的執行時間相等。
我們看一下 processSelectedKeys() 方法, 因為 selectedKeys != null 所以進入 processSelectedKeysOptimized() 方法。
由於沒有這裡只是啟動服務端,沒有客戶端接入進來,所以我們先跳過processSelectedKeys(),一會我們結合客戶端接入來講這裡。
直接看 runAllTasks() 方法。
Runnable task = pollTask(); 這個就是從 taskQueue 中拿出一個task。
然後迴圈執行這個任務, safeExecute(task)。
這個方法也是很簡單,就是直接執行Runnable介面中的run()方法(這裡並不是啟動一個執行緒,而是僅僅的執行一個普通的run方法)。
大家想一下這裡的這個task應該是什麼呢?
大家還記得這段程式碼嗎? 就是這個 register0() 方法。
我們先進入到 doRegister() 方法
繼續傳入當前的eventloop中的selector, opt = 0, 第三個引數 this 就是當前的 NioServerSocketChannel。 進入register 方法
大家看我圈出來的這一句,熟悉嗎?我當時將NIO的時候是不是講到了。
這裡就是把當前的channel註冊到這個多路複用器上。並且把 NioServerSocketChannel 傳進去當做附件 attach, 註冊的 interestOps = 0
好了,當執行完task,由於是一個死迴圈,那麼會繼續執行剛剛的整個過程。
好了,總結一下: 也就是說有一個執行緒一直在這裡不斷迴圈的等待新的 selectionKey中ready的事件,如accept、connect、read、write等。 如果有待處理的task,將會去優先處理的task.
一會我們會啟動一個客戶端看一下是怎麼互動的。
整個註冊完成之後,接下來就是 繫結埠 ,將服務對外開放出去。
我們看下AbstractBootstrap中的 doBind() 方法。
由於整個註冊過程是非同步的,所以這裡 regFuture.isDone() 是否已經完成,如果完成直接執行doBind0(),如果沒有完成,那麼就監聽非同步響應方法,等待成功之後,再執行doBind0()方法。
我們進入doBind0()方法
我們看其實就是向eventLoop中的任務佇列中新增一個task。
這裡我們debug來看一下
另外在 AbstractBootstrap中打一個斷點,在這裡等待註冊事件先完成。
好的,我們啟動服務端。
斷點進來了, 我們再在 NioEventLoop 中打一個斷點,因為這裡是處理task的地方
我們發現有一個主執行緒,一個子執行緒,如下圖
切換到子執行緒,我們看下 task 的執行過程。
因為switch中的hasTask() 是true,那麼我們就直接看
從任務佇列中取出一個task,我們看到就是剛剛我們的那個任務。然後通過safeExecute(task)執行run方法
繼續F5。我們看進入到了runnable中的run方法。
接下來就是一段鏈式呼叫,鏈式訪問pipleline中的handler TailContext -> ServerBootstrapAcceptor -> LoggingHandler -> HeadContext
TailContext 和 ServerBootstrapAcceptor 中沒有bind方法,直接進入LoggingHandler的bind方法,打一個日誌
繼續f5進入到 HeadContext中的bind方法
先判斷是否啟用,如果沒有,則稍後鏈式呼叫handlers中的 channelActive()方法。
進入doBind方法
ok,到這裡繫結埠成功。
目前為止,Server服務端啟動完成,接下來我們看一下,一個客戶端是怎麼接入進來並且進行讀寫操作的。