1. 程式人生 > >Netty原始碼分析--Channel註冊&繫結埠(下)(七)

Netty原始碼分析--Channel註冊&繫結埠(下)(七)

      接下來,我們看到的就是兩個非常重要的方法

      

      就是 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服務端啟動完成,接下來我們看一下,一個客戶端是怎麼接入進來並且進行讀寫操作