1 概述
同時監視多個I/O條件,在其中任意一個就緒時通知進程,這樣的能力稱爲I/O複用,由select和poll函數支持,較新的還有Posix中的pselect函數。(Linux中還多出了epoll)
應用場合:
1. 同時處理多個描述符時,必須用。
2. 同時處理多個套接字時,比較少見。
3. 既要處理監聽套接字,又要處理已連接套接字。
4. 既要處理TCP,又要處理UDP。
5. 要處理多個服務器或多個協議。
2 I/O模型
Unix可用的5種I/O模型:
1. 阻塞式I/O;
2. 非阻塞I/O;
3. I/O複用;
4. 信號驅動式I/O(SIGIO);
5. 異步I/O(Posix的aio_系列函數)。
一個輸入操作一般包括兩個階段:等待數據、從內核和進程複製數據。
2.1 阻塞式I/O模型
直到複製完成或發生錯誤才返回,如被信號打斷。
2.2 非阻塞式I/O模型
無數據時直接返回錯誤,不會陷入睡眠,有數據時直接複製數據。
2.3 I/O複用模型
阻塞在select或poll上,而不是阻塞在真正的I/O系統調用上。另一種I/O模型是在多線程中使用阻塞I/O,每個線程處理一個文件描述符,這樣類似於select,但更自由,不過開銷也更大。
2.4 信號驅動式I/O模型
讓內核在描述符就緒時發送SIGIO信號通知我們。
2.5 異步I/O模型
Posix規定了一族以aio_開頭的函數。這些函數的工作機制是:告知內核啓動某個操作,並讓內核在整個操作完成後通知我們。
與信號I/O的區別是:信號I/O是內核通知何時可以啓動一個操作,異步I/O通知I/O操作何時完成。
完成後會產生指定的信號。
2.6 同步I/O和異步I/O對比
Posix的定義:
1. 同步I/O操作導致請求進程阻塞,直到I/O操作完成;
2. 異步I/O操作不導致請求進程阻塞。
前4種模型都是同步I/O模型,因爲都會在兩個階段中至少發生一次阻塞,只有異步I/O模型與此定義的異步I/O匹配。
3 select函數
超時時間:參數爲NULL則會永久等待,爲0則會立即返回。
BSD的內核不自動重啓被信號中斷的select。
select可監視的描述符最多爲FD_SETSIZE個,一般是1024。它是以輪詢的方式查詢各個描述符狀態,因此設置恰好爲最大的描述符+1的maxfd參數,能減少輪詢花費的時間。
中間的三個參數readset、writeset和exceptset指定要測試讀、寫、異常條件的描述符,都是值-結果參數。目前支持的異常條件只有兩個:
1. 某個套接字的帶外數據的到達。
2. 某個已置爲分組模式的僞終端存在可從其主端讀取的控制狀態信息。
這三個參數都爲空的話,select就是一個比sleep更精確的定時器。
每次重新調用select時都得再次把所有描述符集內所關心的位均置爲1。
3.1 描述符就緒條件
滿足下列條件的套接字準備好讀:
1. 接收緩衝區中的數據字節數大於等於它的低水位標記。可用SO_RCVLOWAT設置此標記,TCP和UDP默認爲1。
2. 接收了FIN的TCP連接,讀操作不阻塞並返回0。
3. 監聽套接字且已完成的連接數不爲0,對它的accept通常不阻塞。
4. 有套接字錯誤待處理,讀操作不阻塞並返回-1,同時設置errno,可以指定SO_ERROR調用getsockopt獲取並清除。
滿足下列條件的套接字準備好寫:
1. 發送緩衝區中的可用空間字節數大於等於它的低水位標記,並且,或者是已連接,或者不需要連接。可用SO_SNDLOWAT來設置該標記,TCP和UDP默認爲2048。
2. 該連接的寫半部關閉,寫這樣的套接字將產生SIGPIPE。
3. 使用非阻塞connect的套接字已建立連接,或者connect已失敗。
4. 有套接字錯誤待處理。
如果一個套接字存在帶外數據或者仍處於帶外標記,它有異常條件待處理。
發生錯誤時,套接字同時可讀可寫。
4 批量輸入
6.4中的str_cli函數仍然不正確。
假設客戶的請求到達服務器和服務器的應答到達客戶都需要8個時間單位,在批量方式運行客戶程序處理多個文件時,客戶是以網絡的最快速度持續發送請求(因爲文件操作比網絡傳輸快得多),在寫完來自文件的最後一個請求時,如果立即關閉連接,管道中未發送的請求和應答就會被丟棄,造成接收的數據大小小於文件大小。
此時需要關閉TCP連接中的一半,即想給服務器發送一個FIN告知數據發送結束,但仍然可以讀此套接字。可由shutdown函數來完成。
stdio有不透明的緩衝區,但select看不到這個緩衝區,因此就不會知道是否有未讀取的數據在其中,可能會在有數據可讀的情況下陷入不必要的阻塞中,此時因爲數據還未處理完,對端不會再發送數據過來,這樣就可能一直阻塞下去。因此select和stdio混用非常容易發生問題。
有自己緩衝區的readline函數有同樣的問題,可以在select前查看緩衝區是否爲空,但因爲文本行可能有一個或多個甚至是半個,程序的複雜性會大大增加。
5 shutdown函數
close有兩個限制可用shutdown避免:
1. close只在描述符的引用計數爲0時才關閉套接字。shutdown可直接正常終止連接。
2. close終止了兩個方向的數據傳送。shutdown可選擇只關一個方向。
用shutdown關閉讀端時,接收緩衝區的數據都被丟棄,該TCP套接字接收的來自對端的任何數據都被確認後丟棄。
關閉寫端時,發送緩衝區的數據將被髮送掉,再正常終止。
讀寫都關閉時,相當於先關閉讀再關閉寫。
6 TCP回射服務器程序(修訂版)
服務器程序只用一個進程來處理所有客戶的請求。
用一個數組來保存所有連接的套接字描述符,初始的讀集合只包括監聽套接字,之後每次accept一次就添加進一個新fd,連接關閉時從集合中去掉這個fd。
如果用readline來接受客戶的請求,可能會遭到拒絕服務型攻擊,用read則不會阻塞於此。
7 pselect函數
pselect相對於select的變化:
1. 使用timespec結構而不是timeval結構,最大精度提高到納秒。
2. 允許在pselect調用時阻塞指定的信號集,在返回時信號掩碼又會重置爲調用前的值。
8 poll函數
與select類似,但處理流設備時能提供額外的信息。
描述符集是通過一個結構體數組和長度提供的,結構體中調用值和返回結果是分開的。返回後依次檢查每個成員的revent值。
等待時間是毫秒數,如果爲0則不等待,爲INFTIM則永遠等待。