在幾年前曾經作過一個網絡項目,當時對網絡通訊僅僅是有點基礎。tcp/ip協議的基礎還算不錯,sockt的應用看起來也不算複雜。因而就用異步非阻塞的sockt通訊實現了服務器端和客戶端。可是項目在聯合調試階段就出現了重大的性能問題。項目的服務器端同時連入的鏈接數在幾百左右,而服務器端的資源消耗很是厲害。就是在這樣的環境下,第一次接觸到高效通訊模型這個概念,IOCP完成端口 (I/O Completion Port)也是在這個時候成爲了我心目中windows平臺下這個概念的最高峯,同時也成爲了個人最大困惑。
做爲當時菜蟲級的程度,找遍了網絡和書店以及msdn,找到了大量關於IOCP的資料,但願能瞭解它的原理和應用。遺憾的是,一開始就先入爲主的認爲IOCP是一種網絡通訊模型,而走入了誤區(都是「端口」惹的禍...),更可悲的是在找到的資料中混雜了「重疊端口」(重疊I/O的另外一種翻譯...找不到具體說法,只是經過代碼發現是一回事),後來又有「重疊I/O」、「完成例成(completion routines)」、「select模式的重疊端口」等類似名稱的資料,終於最後在海量的資料裏面迷失了。都怪本身e文太爛啊。。。。c++
理一理這樣的一批概念:windows
同步、異步、阻塞、非阻塞
select模式
重疊I/O(Overlapped I/O)
完成例成(completion routines)
IOCP完成端口 (I/O Completion Port)服務器
上面的概念我不知道該用什麼來統稱它們,只能說它們都和網絡開發相關。比較混亂的說,而寫這個文章的目的也是想理一理這些概念,作作頭腦風暴。網絡
同步、異步、阻塞、非阻塞是IO的基本原理。同步和異步是針對功能的執行順序來講的,而阻塞和非阻塞是針對等待IO數據的方式說的。所以這是兩對概念,同步與阻塞,異步與非阻塞都沒有必然的聯繫。通俗的說,同步就是工做線程在處理IO時等待IO完成再繼續後面的工做;異步就是工做線程不等待IO處理的結果就繼續後面的工做,而IO處理結果將經過回調方式返回;阻塞是在等待IO時,若是IO沒有可用數據或數據沒有傳送完成,那麼一直等待下去,直到IO處理完數據再返回;非阻塞就是無論IO是否有可用數據或數據已經傳送,照樣返回。只要把同步和阻塞分清,這四個概念就很容易理清了。數據結構
同步、異步、阻塞、非阻塞等原理肯定了網絡通訊的基本網絡通訊模型結構。它們處理的怎麼等待數據和怎麼收發數據的問題,可是對通訊性能並無提供更多的指導。特別是服務端,當成千上萬的鏈接發生併發時,下降cpu的佔用率,減小內存使用率,減小帶寬就成爲了很關鍵的問題。所以便有先有了重疊I/O(Overlapped I/O)。併發
重疊I/O(Overlapped I/O)是怎麼一回事呢?
若是應用程序投遞了一個10KB大小的緩衝區來接收數據,且數據已經到達套接字,則該數據將直接被拷貝到投遞的緩衝區。而阻塞、select、WSAAsyncSelect以及WSAEventSelect等4種模型種,數據到達並拷貝到單套接字接收緩衝區中,此時應用程序會被告知能夠讀入的容量。當應用程序調用接收函數以後,數據才從單套接字緩衝區拷貝到應用程序的緩衝區,差異就體現出來了。
我想之因此叫重疊也就是由於它是兩個緩衝重疊的意思。就這麼一重疊就剩了很多空間和很多事情。app
提重疊I/O(Overlapped I/O)以前應該先提select模式,由於它是最接近同步、異步、阻塞、非阻塞模式的一種模式。select模式、WSAAsyncSelect以及WSAEventSelect都是經過輪詢socket列表來肯定那個socket是當前有效的(它們的原理仍是有點不同的)。好處是防止在在阻塞模式的套接字裏被鎖死,避免在非阻塞套接字裏重複檢查WSAEWOULDBLOCK錯誤。
也就是說,select模式提供了一種比較優秀的查詢方式來判斷當前鏈接的狀態,避免了直接使用阻塞或者非阻塞所致使的問題。異步
提重疊I/O(Overlapped I/O)提供了比select模式更優秀的緩衝管理,那它是否是也提供了比select更優秀的輪詢方式呢?能夠說是,也能夠說不是。由於提重疊I/O(Overlapped I/O)並無直接提供這樣的方法,而是提供了一種手段。這手段是在Overlapped數據結構裏標記了該數據所屬的socket和有數據到的來。怎麼捕獲這個事件?那就是完成例成(completion routines)和事件對象通知(event object notification) 所幹的事情了。完成例成(completion routines)相似於dotnet裏面的異步,在socket接收數據時給WSARecv傳送一個完成後回調的函數句柄,來達到捕獲數據到來的事件;至於事件對象通知(event object notification) 利用的是Overlapped數據結構裏包含「有數據到來的事件」的WSAEVENT hEvent成員與事件句柄關聯實現的,這個就象是使用了類爲外部提供事件通知的方式。socket
使用事件或者是回調都要比輪詢高效。所以重疊I/O(Overlapped I/O)能夠支持很高的鏈接數,傳說能夠上萬。可是重疊I/O(Overlapped I/O)也有不足的地方。重疊緩衝是很好的作法,可是對應每一個socket都要有一個緩衝,那麼一萬個鏈接就會有一萬個緩衝了。另外還有就是不管是異步仍是事件來喚醒線程,都和緩衝存在一個鏈接一個線程的狀況。不面對大量的併發,這兩個問題並不明顯,可是若是併發鏈接數量達到某個級別,cpu和內存都將存在嚴重的浪費。並且其支持的鏈接數與硬件性能的比只能是一個固定值。tcp
爲了解決重疊I/O的問題,便有了IOCP完成端口 (I/O Completion Port)。爲了解決大量併發產生的資源浪費,完成端口引入了相似線程池和資源自動分配回收的兩種技術(這裏之因此說是相似,是由於對於IOCP的原理尚未做更深刻的研究說不許其準確的原理,只能經過它的現象來認爲那是線程池+高效調度+資源回收綜合起來的)。所謂的大量併發鏈接不是指同一時間內的鏈接數量,而是在某時間區間內的鏈接數量。所以在該時間區間內並非全部的鏈接線程都是活動的,只要具備高效的線程分配和調度,那麼在每一個時刻提供比整個區間內鏈接線程小得多的活動線程也能把該時間區間內的併發處理,這就是線程池的好處。對於大量的併發短鏈接,其緩衝絕對是可重複使用的,其線程也是可重複使用的,所以準確高效的資源回收能是資源最大效率的運用起來。經過對線程和緩衝的有效再利用使完成端口在更小的資源環境下取得比重疊I/O更好的性能。
完成端口這個名字使很多人迷惑,咱們徹底能夠經過它的原理和應用來理解這個名字。完成端口是經過調度線程和提供資源重用來取得高效的,所以完成端口把鏈接的調度和其緩衝的分配都進行了封裝,提供了很容易使用的接口。我想完成端口的「完成」即是這樣而來--調度和資源分配的自動完成,「端口」--就是指IO端口,每一個IO設備都有其本身的IO號,這個IO號又被成爲IO的端口。那英文名裏面的I/O又是什麼回事呢?這和重疊I/O是同樣的,就是指它們是應用在I/O上的一種技術。廣義的說,網絡不也是IO?
重疊I/O和IOCP完成端口確實是能夠用在全部的IO應用上。而完成端口由於封裝了調度和回收機制,還能夠把它看成高效的隊列處理技術和線程調度技術來使用。
這是我對這些概念的理解,可能理解得並不許確。上面說起的網絡模型都是在windows平臺上提供了API支持的,能找到的示例代碼大部分也是c++的代碼。在接觸完成端口時的想法是如何把它用在dotnet上,所以在研究完成端口的原理和應用時,也就一直在思考可否在dotnet上使用這麼一個高效的模型。固然不只僅是完成端口,select模式,重疊端口等在dotnet上也是少見的。所以後面就這個方向進行一下探討。
另外,CodeProject上有個託管環境下的IOCP例子很值得研究。
Managed I/O Completion Ports (IOCP)
Managed I/O Completion Ports (IOCP) - Part 2待續....