tomcatserver解析(五)-- Poller

在前面的分析中介紹過,Acceptor的做用是控制與tomcat創建鏈接的數量,但Acceptor僅僅負責創建鏈接。socket內容的讀寫是經過Poller來實現的。
 
Poller使用java nio來實現鏈接的管理。

關於nio。主要需要明白三個概念:Channel、Selector和SelectionKey.
在這裏的使用上。它們之間的關係可以簡單這樣理解,Channel必須註冊到Selector上才幹用於接收socket數據。在Selector上有數據到達的Channel可以用SelectionKey來表示

[註冊]
Poller使用nio來進行socket數據的讀寫,一個經過Poller的register方法,註冊到Poller上。

對Poller的註冊。先進入Poller內部維護的一個事件隊列上。

Poller線程在運行過程當中會去檢查隊列,將channel註冊到selector上。爲了保證在多線程同一時候訪問時數據的一致性,這個隊              java

     列是一個SynchronizedQueue,使用synchronized來保證對隊列中數據的一致性。
註冊的時候。每個channel會有KeyAttachment對象,用來進行channel上的多線程併發運行時的控制。

ServerSocketChannel創建鏈接是在Acceptor上。

[隊列]
隊列定義例如如下  private  final  SynchronizedQueue<PollerEvent> events =  new  SynchronizedQueue<>();

隊列中的每個元素是PollerEvent對象,它攜帶完畢的處理channel相關的信息。

每個事件在處理過程當中。會依據事件的狀態。來實現Channel到Selector上的註冊。隊列處理完畢後,每個註冊到Poller上的channel就完畢了到Selector上的註冊。緩存


[socket數據讀取]
Poller線程的run方法的主題部分使用while(true)的無限循環來運行。當所屬的Endpoint正常運行的時候,在每次運行過程當中,處理其事件隊列,調用selector來讀取數據,而後處理讀取到的數據。
在run方法中。會調用 events方法來處理事件隊列
調用selector.selectNow或selector.select(xxx)來獲取有數據到達的channel

[Poller緩存]
在Poller中使用的緩存是來其所屬的Endpoint的緩存。keyCache和eventCache

eventCache是PollerEvent事件的緩存,在Poller上註冊的時候,從eventCache中取出PollerEvent對象。重置這個對象。而後再放入Poller的事件隊列中。Poller在處理隊列的過程當中。每從隊列中取出一個要處理的PollerEvent事件,處理完以後,把這個PollerEvent對象放回緩存中。   ---- 避免頻繁地建立PollerEvent對象和GC回收。


keyCache是相應的socket信息的緩存,在Poller上註冊的時候。從keyCache中取出KeyAttachment對象。重置這個對象,做用附件用於channel到selector上的註冊。在Processor處理完數據以後。將這個KeyAttachment對象放回keyCache中。  ----- 避免頻繁地建立KeyAttachment對象和GC回收。


[多線程併發控制]
events隊列。爲SychronizedQueue<PollerEvent>,SychronizedQueue提供的offer、poll、size和clear方法都使用了sychronizedkeyword進行修飾,用來保證同一時刻僅僅有一個線程能對隊列進行讀寫。

系統中是同一時候有多個Poller線程在執行的。每個Polle線程有各自的events隊列。但每個Poller線程可能同一時候被多個Acceptor線程調用進行註冊。


[屬性說明]

Poller的屬性例如如下


        private  Selector selector;
         private   final  SynchronizedQueue<PollerEvent> events =
                 new  SynchronizedQueue<>();

         private   volatile  boolean  close =  false ;
         private   long  nextExpiration = 0;  //optimize expiration handling

         private  AtomicLong wakeupCounter =  new  AtomicLong(0);

         private   volatile  int  keyCount = 0;

selector,java nio必備組成部分
events 當前Poller的事件隊列,主要是channel註冊事件
close 當前Poller是否可用的狀態開關
nextExpiration 當前鏈接到此Poller上的socket超時的時限點。

Poller線程在其run方法的每遍運行過程當中。會調用timeout方法來檢查當前鏈接的socket,是否達到了超時的時限,假設達到了超時的時限。則告訴client鏈接超時。tomcat

每次運行完timeout方法後。會又一次設置nextExpiration的值多線程

wakeupCounter的做用:一、告訴Poller當前有多少個新鏈接,這樣當Poller進行selector的操做時,可以選擇是否需要堵塞來等待讀寫請求到達。

二、標識Poller在進行select選擇時。是否有鏈接到達。併發

假設有,就讓當前的堵塞調用立刻返回socket

這個地方比較隱晦,結合代碼來進行解釋

     channel註冊到Poller時運行的部分代碼
       private   void  addEvent(PollerEvent event) {
            events.offer(event);
             if  ( wakeupCounter.incrementAndGet() == 0 ) selector.wakeup();
        }

     Poller的run方法部分代碼

                if  (wakeupCounter.getAndSet(-1) > 0) {
               //if we are here, means we have other stuff to do
                     //do a non blocking select
                    keyCount = selector.selectNow();
                }  else  {
                    keyCount = selector.select(selectorTimeout);
                }
                wakeupCounter.set(0);

考慮如下的兩個場景:
做用一。幫助Poller選擇select方法
在run運行時,當前已經有了5個channel註冊到Poller上。因此 wakeupCounter.getAndSet(-1) > 0 條件知足,Poller調用selector的非堵塞模式的select方法被調用
做用二,讓當前堵塞的select方法立刻返回
在run運行時,假設當前沒有channel註冊到Poller上, wakeupCounter.getAndSet(-1) > 0  條件不知足。但wakeupCounter的值已經被設爲-1了。Poller調用堵塞的select方法。在這期間,假設有新的channel註冊進來,則   wakeupCounter.incrementAndGet() == 0條件知足。select.wakeup方法被調用。讓 selector.select(selectorTimeout)方法立刻返回。
keyCount 註冊到Poller的channel中,I/O狀態已經OK的的個數
相關文章
相關標籤/搜索