linux網絡編程之socket(八):五種I/O模型和select函數簡介

一、五種I/O模型

1、阻塞I/O


我們在前面所說的I/O模型都是阻塞I/O,即調用recv系統調用,如果沒有數據則阻塞等待,當數據到來則將數據從內核空間(套接口緩衝區)拷貝到用戶空間(recv函數提供的buf),然後recv返回,進行數據處理。


2、非阻塞I/O


我們可以使用 fcntl(fd, F_SETFL, flag | O_NONBLOCK); 將套接字標誌變成非阻塞,調用recv,如果設備暫時沒有數據可讀就返回-1,同時置errno爲EWOULDBLOCK(或者EAGAIN,這兩個宏定義的值相同),表示本來應該阻塞在這裏(would block,虛擬語氣),事實上並沒有阻塞而是直接返回錯誤,調用者應該試着再讀一次(again)。這種行爲方式稱爲輪詢(Poll),調用者只是查詢一下,而不是阻塞在這裏死等,這樣可以同時監視多個設備:

while(1) 

非阻塞read(設備1); 

if(設備1有數據到達) 

處理數據; 

非阻塞read(設備2); 

if(設備2有數據到達) 

處理數據; 

..............................

}


如果read(設備1)是阻塞的,那麼只要設備1沒有數據到達就會一直阻塞在設備1的read調用上,即使設備2有數據到達也不能處理,使用非阻塞I/O就可以避免設備2得不到及時處理。

非阻塞I/O有一個缺點,如果所有設備都一直沒有數據到達,調用者需要反覆查詢做無用功,如果阻塞在那裏,操作系統可以調度別的進程執行,就不會做無用功了,在實際應用中非阻塞I/O模型經常與IO multiplexing 一起使用。


3、I/O複用


用select來管理多個I/O,當沒有數據時select阻塞,如果在超時時間內數據到來則select返回,再調用recv進行數據的複製,recv返回後處理數據。


4、信號驅動I/O


先註冊SIGIO信號的處理函數,進程繼續執行其他操作,當數據到來時會發送SIGIO信號給進程,然後可以在信號處理函數中調用recv進行數據的複製,然後recv返回進行數據處理。


5、異步I/O


aio_read 函數也會提供一個buf,系統調用進入內核,如果沒有數據則立即返回,進程繼續執行其他操作,所以叫異步I/O,當數據到來時內核自動複製數據,然後推送給用戶空間,通過在aio_read中指定的信號通知進程,讓其處理數據。異步I/O跟信號驅動I/O的不同之處在於,它不用調用recv進行數據的複製,如果將後者比做”拉pull“,則前者可以認爲是”push推“,push的效率會高點,其實異步I/O跟windows下面的完成端口差不多,但aio_read的實現或多或少存在問題,用得也比較少。實踐中用得比較多的如boost 庫的asio 也是異步IO。

  

腳註:同步和異步的區別在於是不是要求處理消息者自己來完成將數據從內核緩衝區複製回進程緩衝區的過程。消息者阻塞和非阻塞應該是發生在消息的處理的時刻。阻塞其實就是等待,發出通知,等待結果完成。非阻塞屬於發出通知,立即返回結果,沒有等待過程。

對unix來講:阻塞式I/O(默認),非阻塞式I/O(nonblock),I/O複用(select/poll/epoll)信號驅動IO都屬於同步I/O,因爲它們在數據由內核空間複製回進程緩衝區時都是阻塞的(不能幹別的事)。只有異步I/O模型(AIO)是符合異步I/O操作的含義的,即在1數據準備完成、2由內核空間拷貝回緩衝區後 通知進程,在等待通知的這段時間裏可以幹別的事。




POSIX defines these two terms as follows:

A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes.
An asynchronous I/O operation does not cause the requesting process to be blocked.
Using these definitions, the first four I/O models—blocking, nonblocking, I/O multiplexing, and signal-driven I/O—are all synchronous because the actual I/O operation (recvfrom) blocks the process. Only the asynchronous I/O model matches the asynchronous I/O definition.


二、select函數簡介


/* According to POSIX.1-2001 */
       #include <sys/select.h>
       /* According to earlier standards */
       #include <sys/time.h>
       #include <sys/types.h>
       #include <unistd.h>


int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);


參數1:讀寫異常集合中的文件描述符的最大值加1;

參數2:讀集合,關心可讀事件;

套接口緩衝區有數據可讀
對等連接的寫一半關閉。即接收到FIN段,讀操作將返回0
如果是監聽套接口,已完成連接隊列不爲空時。
套接口上發生了一個錯誤待處理,錯誤可以通過getsockopt指定SO_ERROR選項來獲取。

參數3:寫集合,關心可寫事件;

套接口發送緩衝區有空間容納數據。(連接一旦建立就可寫)

對等連接的讀一半關閉。即收到RST段之後,再次調用write操作。

套接口上發生了一個錯誤待處理,錯誤可以通過getsockopt指定SO_ERROR選項來獲取。

參數4:異常集合,關心異常事件;

套接口存在帶外數據(TCP頭部 URG標誌,16位緊急指針字段)

參數5:超時時間結構體


對於參數2,3,4來說,如果不關心對應事件則設置爲NULL即可。注意5個參數都是輸入輸出參數,即select返回時可能對其進行了修改,比如集合被修改以便標記哪些套接口發生了事件,時間結構體的傳出參數是剩餘的時間,如果設置爲NULL表示永不超時。用select管理多個I/O,select阻塞等待,一旦其中的一個或多個I/O檢測到我們所感興趣的事件,select函數返回,返回值爲檢測到的事件個數,並且返回哪些I/O發送了事件,遍歷這些事件,進而處理事件。注意當select阻塞返回後,此時調用accept 接收連接是不會阻塞的,直接返回已連接套接字,可以認爲是select 提前阻塞了。但此時調用write 還是可能阻塞的,因爲需要寫入的空間大小可能緩衝區還不滿足。


下面是4個可以對集合進行操作的宏:
void FD_CLR(int fd, fd_set *set); // 清除出集合
int  FD_ISSET(int fd, fd_set *set); // 判斷是否在集合中
void FD_SET(int fd, fd_set *set); // 添加進集合中
void FD_ZERO(fd_set *set); // 將集合清零


RETURN VALUE
       On success, select() return the number of file descriptors contained in the three returned descriptor sets (that is,
       the total number of bits that are set in readfds, writefds, exceptfds) which may be zero if the timeout  expires  before  anything
       interesting  happens.   On error, -1 is returned, and errno is set appropriately; the sets and timeout become undefined, so do not
       rely on their contents after an error.


select 函數的舉例應用看這裏


原文鏈接:點擊打開鏈接


發佈了183 篇原創文章 · 獲贊 234 · 訪問量 126萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章