在前面的分析中介紹過,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的的個數