非阻塞服務器模型最重要的一個特色是,在調用讀取或寫入接口後當即返回,而不會進入阻塞狀態。在探討單線程非阻塞IO模型前必需要先了解非阻塞狀況下Socket事件的檢測機制,由於對於非阻塞模式最重要的事情是檢測哪些鏈接有感興趣的事件發生,通常會有以下三種檢測方式。服務器
如圖所示,當多個客戶端向服務器請求時,服務器端會保存一個socket鏈接列表,應用層線程對socket列表進行輪詢嘗試讀取或寫入。對於讀取操做,若是成功讀取到若干數據則對讀取到的數據進行處理,讀取失敗則下個循環再繼續嘗試;對於寫入操做,先嚐試將數據寫入指定的某個socket,寫入失敗則下個循環再繼續嘗試。socket
這樣看來,無論多少個socket鏈接均可以被一條線程管理起來,一條線程負責遍歷這些socket列表,不斷地嘗試讀取或寫入數據,很好地利用了阻塞的時間,處理能力獲得提高。但這種模型須要在應用程序中遍歷全部的socket列表,同時須要處理數據的拼接,鏈接空閒時可能也會佔用較多CPU資源,不適合實際使用。對此改進是使用事件驅動的非阻塞方式。函數
這種方式將socket的遍歷工做交給了操做系統內核,對socket遍歷的結果組織成一系列的事件列表並返回應用層處理。對於應用層,他們須要處理的對象就是這些事件,這就是其中一種事件驅動的非阻塞方式的實現。優化
如圖所示,服務器端有多個客戶端鏈接,應用層向內核請求讀寫事件列表。內核遍歷全部socket並生成對應的可讀列表readList和可寫列表writeList,readList標明瞭每一個socket是否可讀,例如socket1的值爲1,表示可讀,socket2的值爲0,表示不可讀。writeList則標明瞭每一個socket是否可寫。應用層遍歷讀寫事件列表readList和writeList,作相應的讀寫操做。spa
內核遍歷socket的方式已經不用在應用層對全部socket進行遍歷,將遍歷工做下移到內核層,這種方式有助於提升檢測效率。可是它須要將全部鏈接的可讀事件列表和可寫事件列表傳到應用層,假如socket鏈接數量大起來的話,列表從內核複製到應用層也是不小的開銷。另外,當活躍鏈接較少時,內核與應用層之間存在不少無效的數據拷貝,由於它是將活躍和不活躍的鏈接狀態都拷貝到應用層了。操作系統
經過遍歷的方式檢測socket是否可讀可寫是一種效率比較低的方式,不論是在應用層遍歷仍是在內核遍歷。因此須要另一種機制來優化遍歷的方式,那就是回調函數。內核中socket都對應一個回調函數,當客戶端往socket發送數據時,內核從網卡接收數據後就會調回調函數將此socket做爲可讀事件加入到事件列表中。應用層獲取此事件列表便可獲得全部感興趣的事件。線程
如圖所示,服務器端有多個客戶端socket鏈接,首先應用層告訴內核每一個socket感興趣的事件;接着當客戶端發送數據過來時,對應會有一個回調函數,內核從網卡複製數據成功後即調回調函數將socket1做爲可讀事件event1加入到事件列表,一樣地,內核發現網卡可寫時就將socket2做爲可寫事件event2添加到事件列表中;最後,應用層向內核請求讀寫事件列表,內核將包含了event1和event2的事件列表返回應用層,應用層經過遍歷事件列表得知socket1有數據待讀取,因而進行讀操做,而socket2則能夠寫入數據。對象
這種方式由操做系統內核維護客戶端的全部鏈接並經過回調函數不斷更新可讀可寫事件列表,而應用層線程只要遍歷這些事件列表便可知道可讀取或可寫入的鏈接,進而對這些鏈接進行讀寫操做。極大提升了檢測效率,天然處理能力也更強。blog
上面介紹了三種非阻塞的事件檢測機制,對於Java來講,非阻塞IO的實現徹底是基於操做系統內核的非阻塞IO,它將操做系統的非阻塞IO的差別屏蔽了並提供統一的API,讓咱們沒必要關心操做系統。JDK會幫咱們選擇非阻塞IO的實現方式,例如對於Linux系統,在支持epoll的狀況下JDK會優先選擇用epoll實現Java的非阻塞IO。這種非阻塞方式的事件檢測機制就是效率最高的「內核基於回調的事件檢測」。接口