linux 服務器socket的五種模型

一、五種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的實現或多或少存在問題,用得也比較少。

  

二、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函數的舉例應用看這裏

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