一、五種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模型比較少用。
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的實現或多或少存在問題,用得 也比較少。
二、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阻塞返回後,此時調用read/write 是不會阻塞的,因為正是有可讀可寫事件發生才導致select 返 回,也可以認為是select 提前阻塞了。
下面是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); // 將集合清零
select函數的舉例應用看這裡。