在進入今天的select模型的主題之前,我們先來簡單了解1下5種I/O模型:
(1)阻塞I/O(默許采取這類方式)
在服務端socket編程中,我們常見的accpet函數、recv函數都是采取的阻塞情勢。以recv為例: 當上層利用App調用recv系統調用時,如果對等方沒有發送數據(Linux內核緩沖區中沒有數據),上層利用Application1將阻塞;當對等方發送了數據,Linux內核recv端緩沖區數據到達,內核會把數據copy給用戶空間。然后上層利用App消除阻塞,履行下1步操作。
(2)非阻塞I/O(不推薦)
上層利用如果利用非阻塞模式, 會循環調用recv函數,接受數據。若緩沖區沒有數據,上層利用不會阻塞,recv返回值為⑴,毛病碼是EWOULDBLOCK(圖中的標記有誤)。上層利用程序不斷輪詢有無數據到來。造成上層利用忙等待。大量消耗CPU。因此非阻塞模式很少直接用。利用范圍小,1般和IO復用配合使用。
(3)信號驅動I/O模型(不常常使用)
上層利用建立SIGIO信號處理程序。當緩沖區有數據到來,內核會發送信號告知上層利用App; 當上層利用App接收到信號后,調用recv函數,因緩沖區有數據,recv函數1般不會阻塞。但是這類用于模型用的比較少,屬于典型的“拉模式(上層利用被動的去Linux內核空間中拉數據)”。即:上層利用App,需要調用recv函數把數據拉進來,會有時間延遲,我們沒法避免在延遲時,又有新的信號的產生,這也是他的缺點。
(4)異步I/O(不經常使用)
上層利用調用aio_read函數,同時提交1個利用層的緩沖區buf;調用終了后,不會阻塞。上層利用程序App可以繼續其他任務; 當TCP/IP協議緩沖區有數據時,Linux主動的把內核數據copy到用戶空間。然后再給上層利用App發送信號;告知App數據到來,需要處理!
異步IO屬于典型的“推模式”, 是效力最高的1種模式,上層利用程序App有異步處理的能力(在Linux內核的支持下,處理其他任務的同時,也可支持IO通訊, 與Windows平臺下的完成端口作用類似IOCP)。
(5)I/O復用的select模型(本篇的重點)
試想如果你遇到下面的問題會怎樣處理?
1)server除要對外響應client的服務外,還要能夠接受標準輸入的命令來進行管理。
假設使用上述阻塞方式,在單線程中,accept調用和read調用一定有前后順序,而它們都是阻塞的。比如先調用accept,后調用
read,那末如果沒有客戶要求時,服務器會1直阻塞在accept,沒有機會調用read,也就不能響應標準輸入的命令。
2) server要對外提供大量的client要求服務。
假設使用阻塞方式,在單線程中,由于accept和recev都是阻塞式的,那末當1個client被服務器accept后,它可能在send發送消息時阻塞,因此服務器就會阻塞在recev調用。即時此時有其他的client進行connect,也沒法進行響應。
這時候就需要select來解決啦!select實現的是1個管理者的功能: 用select來管理多個IO, 1旦其中的1個IO或多個IO檢測到我們所感興趣的事件, select就返回, 返回值就是檢測到的事件個數, 并且由第2~4個參數返回那些IO發送了事件, 這樣我們就能夠遍歷這些事件, 進而處理這些事件。
有人說,我用多線程不就能夠了嗎?但是在UNIX平臺下多進程模型善于處理并發長連接,但卻不適用于連接頻繁產生和關閉的情形。固然select其實不是最高效的,有著O(N)的時間復雜度,關于更高效的epoll我將在后面的博客中繼續講授,歡迎大家關注,