Linux I/O 模型---I/O複用:Select和Poll函數

Select和Poll函數

在前一章中,我們遇到一個問題就是,客戶端阻塞在了從標準輸入中讀取數據,與此同時,服務器由於某種原因要求關閉連接給客戶端發送了一個FIN,而客戶端只有從標準輸入讀到數據返回後才能知道連接已關閉。在這一章中介紹的I/O複用可以很好的解決這個問題。
1.I/O模型
a. Unix下共有五種I/O模型
阻塞I/O
非阻塞I/O
I/O複用(select和poll)
信號驅動I/O(SIGIO)
異步I/O(Posix.1的aio_系列函數)
b.阻塞I/O模型
應用程序調用一個IO函數,導致應用程序阻塞,等待數據準備好。
如果數據沒有準備好,一直等待。。。。
數據準備好了,從內核拷貝到用戶空間
IO函數返回成功指示

 

c.非阻塞I/O模型
我們把一個套接口設置爲非阻塞就是告訴內核,當所請求的I/O操作無法完成時,不要將進程睡眠,而是返回一個錯誤。這樣我們的I/O操作函數將 不斷的測試數據是否已經準備好,如果沒有準備好,繼續測試,直到數據準備好爲止。在這個不斷測試的過程中,會大量的佔用CPU的時間。
d. I/O複用模型
I/O複用模型會用到select或者poll函數,這兩個函數也會使進程阻塞,但是和阻塞I/O所不同的的,這兩個函數可以同時阻塞多個I/O操作。而且可以同時對多個讀操作,多個寫操作的I/O函數進行檢測,直到有數據可讀或可寫時,才真正調用I/O操作函數。
e.信號驅動I/O模型
首先我們允許套接口進行信號驅動I/O,並安裝一個信號處理函數,進程繼續運行並不阻塞。當數據準備好時,進程會收到一個SIGIO信號,可以在信號處理函數中調用I/O操作函數處理數據。
f.異步I/O模型
調用aio_read函數,告訴內核描述字,緩衝區指針,緩衝區大小,文件偏移以及通知的方式,然後立即返回。當內核將數據拷貝到緩衝區後,再通知應用程序。
2.幾種I/O模型的比較
前四種模型的區別是第一階段基本相同,第二階段基本相同,都是將數據從內核拷貝到調用者的緩衝區。而異步I/O的兩個階段都不同於前四個模型。
3.同步I/O和異步I/O
a.同步I/O操作引起請求進程阻塞,直到I/O操作完成。
異步I/O操作不引起請求進程阻塞。
b.我們的前四個模型都是同步I/O,只有最後一個異步I/O模型是異步I/O。
4.Select函數
a. Select函數可以指示內核等待多個事件中的任一個發生,並僅在任一個事件發生或經某個指定的時間後才返回,才喚醒進程
b. 可以調用select函數,通知內核在下列情況發生時才返回:
集合{1,4,5}中的任何描述符準備好讀,或者
集合{2,7}中的任何描述符準備好寫,或者
集合{1,4}中任何描述符有異常條件待處理,或者
已經經過了10.2秒
c.描述字可以不受限制與套接字,任意的描述符都可以用select來測試
d. 函數原型
int select(int maxfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct tim *timeout);
參數說明:
timeout:告訴內核等待任一描述符準備好可以花費的時間,這裏會有三種情況,
第一種情況是timeout是NULL,這樣將一直等待,直到某個描述符準備好;
第二種情況是timeout的值是0,那麼將不等待,立即返回;
第三種情況是timeout中的秒或微秒被賦值,那麼將等待指定的時間。
此外,如果進程收到一個信號,select也會被中斷返回。
readfds,writefds和exceptfds指定了讓內核測試讀,寫和異常條件所需的描述字。
maxfds:說明了被測試的描述符的個數,它的值是要被測試的最大的描述符加1.
返回值:所有描述符集的已準備好的總位數。返回時,描述符集中任何沒有準備好的描述符都被清0,我們用FD_ISSET來測試是哪個描述符準備好了。因此,每次調用select時,都要重新將我們關心的描述符在描述符集中置爲1.
e. fd_set說明
fd_set是一個整數數組,每個數中的每一位對應一個描述符。例如用32位表示一個整數,那麼數組的第一個元素對應於描述字0~31,第二個元素對應於描述字32~63.
四個相關的宏:
FD_CLR(int fd, fd_set *set);
FD_ISSET(int fd, fd_set *set);
FD_SET(int fd, fd_set *set);
FD_ZERO(fd_set *set);
f.描述符準備好讀的條件:
套接口緩衝區中的數據字節大於等於套接口接收緩衝區低潮限度的當前值。套接口將不阻塞並返回一個大於0的值,就是當前準備好讀的數據字節數。
套接口收到一個FIN,套接口的讀操作將返回一個0.
套接口是一個監聽套接口,並且以完成的連接數非0。
有一個套接口錯誤待處理。套接口的讀操作將返回-1.
g.描述符準備好寫的條件:
套接口發送緩衝區的可用空間大於等於套接口發送緩衝區低潮限度的當前值。,且或者套接口以連接,套接口不要求連接。
套接口寫這一半關閉,這樣將產生一個SIGPIPE錯誤。
有一個套接口錯誤待處理。
h.描述符異常的條件:
套接口存在帶外數據
仍處於帶外標記
i. 每個進程可以使用的最大描述符
有一個宏FD_SETSIZE定義了一個進程可以使用的最大描述符數。如果要更改這個值不僅僅要在定義的頭文件中改變,還要重新編譯內核。
5.使用select函數修改前面的客戶-服務器程序
在前面的客戶-服務器程序中,客戶端採用的是停-等這樣的策略來接收來自標準輸入的用戶輸入,這樣的好處是可以一對一的完成從用戶輸入,然後讀 取從服務器返回的字符串,這樣的弊端是當程序阻塞在等待用戶輸入時,無法及時的處理來自服務器的FIN等這些消息。現在我們用select函數將客戶端程 序做一些修改,使能避免前面提到的問題。
另外,服務器也採用select函數,從而避免產生過多的進程,使用select後可以只有一個進程就可以處理多個客戶端。在服務器端建立一個 整數型的數組,用來存放已經完成的客戶端連接。每次從見天套接口讀到數據後,我們將新的來自客戶端的連接加入到這個數組中,並且修改maxfd的值。每次 從客戶端套接口讀到數據後,將讀到的數據重新寫回到客戶端套接口。
a.服務器從客戶套接口讀到數據後,返回值有可能爲0,這說明客戶端已經關閉了寫這個方向的連接。在將數據寫入客戶端套接口後,要將連接關閉。並將客戶連接從存放客戶連接的數組中移除。
b.採用上面的方案,存在一個潛在的問題就是可能受到拒絕服務的攻擊。一個惡意用戶和服務器建立連接,發送單個字符,但是沒有發送換行符或者終 止,這樣服務器將阻塞在read函數中。可能的解決辦法是採用非阻塞I/O或者讓每個客戶用單獨的進程來處理或者爲I/O操作設置超時。
c.客戶端接收到EOF時,只能關閉寫這個方向的連接,因爲我們仍然希望讀取來自服務器的數據。此時是不能用close來關閉連接的,而要用 shutdown來關閉。如果套接字的訪問計數大於0,那麼close只是將計數減1;如果套接字的訪問計數等於0,close將終止套接字的兩個方向, 那樣我們將不能讀取仍然沒有從服務器發送回來的數據。
6.shutdown函數
int shutdown(int s, int how);
參數說明:
s: 代表套接字描述字
how:SHUT_RD -- 關閉套接字的讀取數據方向的連接
SHUT_WR -- 關閉套接字的寫入數據方向的連接
SHUT_RDWR -- 關閉套接字雙向的連接
7.pselect函數
int pselect(int n, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, const struct timespec *timeout, const
sigset_t *sigmask);
a.pselect函數採用timespec結構,這個結構支持納秒
b.sigmask是信號掩碼,將禁止遞交某些信號
8.poll函數
int poll(struct pollfd *ufds, unsigned int nfds, int time­out);
a.參數說明
ufds: 是一個struct pollfd結構體的指針
nfds: 說明我們關心的描述字的個數
timeout: 超時等待的時間,單位是毫秒
b.struct pollfd結構體說明
struct pollfd {
int fd;
short events;
short revents;
};
fd: 是描述字
events: 是在描述字上關心的事件
revents: 是在描述字上返回的事件
poll函數返回後我們要測試revents中的事件是否是我們關心的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章