咱們看到上面的TCP客戶同時處理兩個輸入:標準輸入和TCP套接字。咱們遇到的問題就是在客戶阻塞於(標準輸入上的)fgets調用期間,服務器進程會被殺死。服務器TCP雖然正確地給客戶TCP發送一個FIN,可是既然客戶進程阻塞於從標準輸入讀入的過程,它將看不到這個ROF,知道從套接字讀時爲止(可能已通過了很長時間)。這樣的進程須要一種預先告知內核的能力,使得內核一旦發現進程指定的一個或多個I/O條件就緒(也就是說輸入已準備好被讀取,或者描述符已能承接更多的輸出),它就通知進程。這個能力成爲I/O複用,是由select和poll這兩個函數支持的。web
I/O複用典型使用在下列網絡應用場合:編程
1)當客戶處理多個描述符(一般是交互式輸入和網絡套接字)時,必須使用I/O複用服務器
2)一個客戶同時處理多個套接字是可能的,不過比較少見。在16.5節結合一個web客戶的上下文給出這種場合使用select的例子網絡
3)若是一個TCP服務器既要處理監聽套接字,又要處理已鏈接套接字,通常就要使用I/O複用異步
4)若是一個服務器既要處理TCP,又要處理UDP,通常就要使用I/O複用。8.15節有這麼一個例子函數
5)若是一個服務器要處理多個服務或者鍍鉻協議(在13.5節講述的inetd守護進程),就要用I/O複用spa
I/O複用並不是只限於網絡編程,許多重要的應用程序也須要使用這項技術指針
在UNIX下可用的5種I/O模型:進程
阻塞式I/O;事件
非阻塞式I/O;
I/O複用(select和poll);
信號驅動式I/O;
異步I/O
在上述所說的那樣,一個輸入操做一般包括兩個不一樣的階段:
1)等待數據準備好;
2)從內核向進程複製數據
對於一個套接字上的輸入操做,第一步一般涉及等待數據從網絡中到達。當所等待分組到達時,它被複制到內核總的某個緩衝區。第二步就是把數據從內核緩衝區複製到應用進程緩衝區。
(1)阻塞時I/O模型:
最流行的I/O模型,本書到目前爲止的全部例子都使用該模型。默認情形下,全部套接字都是阻塞的。
使用UDP而不是TCP爲例子的緣由在於就UDP而言,數據準備好讀取的概念比較簡單:要麼整個數據報已經收到,要麼尚未。對於TCP而言,諸如套接字低水位標記等額外變量開始起做用,道指這個概念複雜。
咱們把recvfrom函數視爲系統調用,由於咱們正在區分應用進程和內核。無論如何實現,通常都會從在應用進程空間中國運行切換到在內核空間中運行,一端時間以後再切換回來。 在上圖中,進程調用recvfrom,其系統調用直到數據報到達且被複制到應用進程的緩衝區中或者發送錯誤才返回。最多見的錯誤是系統調用被信號中斷,咱們說進程在從調用recvfrom開始到它返回的整段時間內是被阻塞的。recvfrom成功返回後,應用進程開始處理數據報。
(2)非阻塞式I/O模型:
進程把一個套接字設置成非阻塞是在通知內核:當全部請求的I/O操做非得把本進程投入睡眠才能完成時,不要把本進程投入睡眠,而是返回一個錯誤。將在16章中詳細介紹非阻塞是I/O
前三次調用recvfrom時沒有數據可返回,所以內核轉而當即返回一個EWOULDBLOCK錯誤。第四次調用recvfrom時已有一個數據報準備好,它被複制到應用進程緩衝區,因而recvfrom成功返回。接着處理數據。
當一個應用進程像這樣對一個非阻塞描述符循環調用recvfrom時,咱們成爲輪詢,應用進程持續輪詢內核,以查看某個操做是否就緒。這麼作每每耗費大量CPU時間,不過這種模型偶爾也會遇到。
(3)I/O複用模型:
有了I/O複用,咱們就能夠調用select或者poll,阻塞在這兩個系統調用中的某一個,而不是阻塞在真正的I/O系統調用上。下圖展現了I/O複用模型
咱們阻塞與select調用,等待數據報套接字變爲可讀。當select返回套接字可讀這一條件時,咱們調用recvfrom把所可讀數據報復制到應用進程緩衝區。比較上面兩圖,I/O複用並不顯得有什麼優點,事實上因爲使用select須要兩個而不是單個系統調用,其優點在於能夠等待多個描述符就緒
(4)信號驅動式U/O模型:
能夠用信號,讓內核在描述符就緒時發送SIGIO信號通知咱們。稱爲信號驅動式I/O
咱們首先開啓套接字的信號驅動式I/O功能,並經過sigaction系統調用安裝一個信號處理函數。該系統調用將當即返回,咱們的進程繼續工做,也就是說它沒有被阻塞。當數據報準備好讀取時,內核就爲該進程產生一個SIGIO信號。咱們隨後既能夠在信號處理函數中調用recvfrom讀取數據報,並通知主循環數據已準備好待處理。也能夠當即通知循環,讓它讀取數據報。
不管如何處理SIGIO信號,這種模型的優點在於等待數據報到達期間進程不被阻塞。主循環能夠繼續執行,只要等待來自信號處理函數的通知:既能夠是數據已準備好被處理,也能夠是數據報已準備好被讀取。
(5) 異步I/O模型:
告知內核啓動某個操做,並讓內核在整個操做(包括將數據從內核複製到咱們本身的緩衝區)完成後通知咱們。這種模型與前一節介紹的信號驅動模型的主要區別在於:信號驅動I/O是由內核通知咱們如何啓動一個I/O操做,而異步I/O模型是由內核通知咱們I/O操做什麼時候完成。
咱們調用aio_read函數,給內核傳遞描述符、緩衝區指針。緩衝區大小和文件偏移,並告訴內核當整個操做完成時如何通知咱們。該系統調用當即返回,並且在等到I/O完成期間,咱們的進程不被阻塞。
前四種模型的主要區別在於第一階段,由於它們的第二階段是同樣的:在數據從內核複製到調用者的緩衝區期間,進程阻塞與recvfrom調用。相反,異步I/O模型在這兩個階段都要處理。
從理論上講,非阻塞IO、阻塞IO、IO複用和信號驅動IO都是同步IO模型。由於這四種IO模型中,IO的讀寫操做,都是在IO事件發生以後,由應用進程來完成的。而POSIX規範所定義的異步IO模型則不一樣。對異步IO而言,用戶能夠直接對IO執行讀寫操做,這些操做告訴內核用戶讀寫緩衝區的位置,以及IO操做完成以後內核通知應用程序的方式。異步IO的讀寫操做老是當即返回,而不論IO是不是阻塞的,由於真正的讀寫操做已經由內核接管。也就是說,同步IO模型要求用戶代碼自行執行IO操做(將數據從內核緩衝區讀入用戶緩衝區,或將數據從用戶緩衝區寫入內核緩衝區),而異步IO機制則由內核來執行IO操做(數據在內核緩衝區和用戶緩衝區之間的移動是由內核在「後臺」完成的)。你能夠這樣認爲,同步IO嚮應用程序通知的是IO就緒事件,而異步IO嚮應用程序通知的是IO完成事件。