UNIX網絡編程卷一 筆記 第六章 第6章 I/O複用

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則永遠等待。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章