一 服務端Channel注冊Selector
當服務端Channel 創建並且初始化完成之后,會將其注冊到 selector,通過語句config().group().register(channel)進行注冊工作,該方法最終調用 AbstractUnsafe 類的 register 方法。以下各圖是服務端Channel注冊到Selector上的函數調用鏈。
截止到此,服務器Channel雖然已經注冊到了Selector,但是注冊時配置的感興趣的事件時0,可以看看doRegister()方法:
而且,這里服務端Channel也還沒有綁定服務器的端口,只是完成了注冊工作,下面看一下綁定服務端端口的操作。
二 服務端Channel綁定服務器端口
服務端Channel的綁定工作是在AbstractBootstrap的doBind0()方法中完成的:
在AbstractBootstrap的doBind0()方法中,給服務端Channel已經綁定的NioEventLoop中添加了一個Runnable任務,這個任務中實現了服務端Channel綁定服務器端口的工作。那NioEventLoop是什么時候去執行這個任務的?這里先說一下NioEventLoop的職責,它主要做兩件事:①處理Channel中已經准備就緒的IO事件;②處理在任務隊列中的非IO任務。服務端Channel綁定服務器的端口就屬於一個非IO任務。並且NioEventLoop在處理IO事件與非IO任務時,可以根據ioRatio這個成員變量來分配處理IO事件與非IO任務的時間,默認情況ioRatio是50,說明分配給兩者的處理時間是一樣的。下面就是NioEventLoop啟動后處理IO事件與非IO任務的代碼,在NioEventLoop類的run方法中,由以下代碼可知,在服務端的NioEventLoop啟動之前,已經將綁定端口的任務添加到了任務隊列,進到下面的for循環時,因為隊列中有任務且還沒有綁定服務器的端口,將執行selectNow方法並且直接返回,在switch中什么也不干,因此在調用processSelectedKeys方法后因為沒有准備就緒的IO事件所以也直接返回,然后執行任務隊列中的任務,完成服務器端口的綁定工作。
for (;;) { // 當taskQueue中有任務時,使用非阻塞的selectNow方法來獲取Channel中准備就緒的IO事件 // 如果Channel中沒有准備就緒的IO事件,selectNow可以快速返回,然后處理taskQueue中的任務 switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) { case SelectStrategy.CONTINUE: continue; case SelectStrategy.SELECT: select(wakenUp.getAndSet(false)); default: } cancelledKeys = 0; needsToSelectAgain = false; final int ioRatio = this.ioRatio; if (ioRatio == 100) { try { processSelectedKeys(); } finally { // Ensure we always run tasks. // 處理在任務隊列中的非IO任務 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); } }
至此,服務端Channel不僅創建、初始化完成,也注冊到了Selector,並且綁定了服務端端口,在這之后,服務器便可以接收客戶端的連接並進行處理了。
