接下來,咱們看到的就是兩個很是重要的方法異步
就是 processSelectedKeys() 和 runAllTasks() 方法了。oop
selectionKey中ready的事件,如accept、connect、read、write等,由processSelectedKeys方法觸發。屬於I/O任務。this
添加到taskQueue中的任務,如register0、bind0等任務,由runAllTasks方法觸發。屬於非I/O任務。線程
兩種任務的執行時間比由變量ioRatio控制,默認爲50,則表示容許非IO任務執行的時間與IO任務的執行時間相等。debug
咱們看一下 processSelectedKeys() 方法, 由於 selectedKeys != null 因此進入 processSelectedKeysOptimized() 方法。3d
因爲沒有這裏只是啓動服務端,沒有客戶端接入進來,因此咱們先跳過processSelectedKeys(),一會咱們結合客戶端接入來說這裏。rest
直接看 runAllTasks() 方法。日誌
Runnable task = pollTask(); 這個就是從 taskQueue 中拿出一個task。blog
而後循環執行這個任務, 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服務端啓動完成,接下來咱們看一下,一個客戶端是怎麼接入進來而且進行讀寫操做的。