I/O多路複用:select、poll、epoll

I/O多路複用

I/O複用使得程序能同時監聽多個文件描述符,這對提高程序的性能至關重要。通常,網絡程序在下列情況下需要使用I/O複用技術:
(1)客戶端程序要同時處理多個socket。比如非阻塞connect技術。
(2)客戶端程序要同時處理用戶輸入和網絡連接。比如一個聊天室程序。
(3)TCP服務器要同時處理監聽socket和連接socket。這是I/O複用使用最多的場合。
(4)服務器要同時處理TCP請求和UDP請求。
(5)服務器要同時監聽多個端口,或者處理多種服務。

需要指出:
I/O複用雖然能同時監聽多個文件描述符,但它本身是阻塞的。並且當多個文件描述符同時就緒時,如果不採取額外的措施,程序就只能按順序依次處理其中的每一個文件描述符,這使得服務程序看起來像是串行工作的。如果要實現併發,只能使用進程或多線程等編程手段。

Linux下實現I/O複用的系統調用主要有select、poll、epoll。

1、select系統調用

select系統調用的用途是:在一段時間內,監聽用戶感興趣的文件描述符上的可讀、可寫和異常等事件。select系統調用的原型如下:

#include<sys\select.h>  
int pollint select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

參數說明:
(1)、select的第一個參數nfds爲fdset集合中最大描述符值加1,fdset是一個位數組,其大小限制爲__FD_SETSIZE(1024),位數組的每一位代表其對應的描述符是否需要被檢查;
(2)、select的第二三四個參數表示需要關注讀、寫、異常事件的文件描述符位數組,這些參數既是輸入參數也是輸出參數,可能會被內核修改用於標示哪些描述符上發生了關注的事件。所以每次調用select前都需要重新初始化fdset。
(3)、timeout參數爲超時時間,該結構會被內核修改,其值爲超時剩餘的時間。

注:
select對應於內核中的sys_select調用,sys_select首先將第二三四個參數指向的fd_set拷貝到內核,然後對每個被SET的描述符調用進行poll,並記錄在臨時結果中(fdset),如果有事件發生,select會將臨時結果寫到用戶空間並返回;當輪詢一遍後沒有任何事件發生時,如果指定了超時時間,則select會睡眠到超時,睡眠結束後再進行一次輪詢,並將臨時結果寫到用戶空間,然後返回。
select返回後,需要逐一檢查關注的描述符是否被SET(事件是否發生)。

2、poll系統調用

poll系統調用和select類似,也是在指定的時間內輪詢一定數量的文件描述符,以測試其中是否有就緒者。

poll與select不同的是:
(1)、poll通過一個pollfd數組向內核傳遞需要關注的事件,故沒有描述符個數的限制,pollfd中的events字段和revents分別用於標示關注的事件和發生的事件,故pollfd數組只需要被初始化一次。
(2)、poll的實現機制與select類似,其對應內核中的sys_poll,只不過poll向內核傳遞pollfd數組,然後對pollfd中的每個描述符進行poll,相比處理fdset來說,poll效率更高。
(3)、poll返回後,需要對pollfd中的每個元素檢查其revents值,來判斷指定事件是否發生

poll的原型如下:

#include<poll.h>  
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

struct pollfd 
{
    int   fd;         /* file descriptor-用來指定文件描述符 */
    short events;     /* requested events-用來告訴poll監聽fd上的哪些事件,它是一系列事件的按位或 */
    short revents;    /* returned events—該成員由內核修改,以通知應用程序fd上實際發生了哪些事件*/
};

3、epoll系統調用

epoll是linux特有的I/O複用函數。它在實現和使用上與select、poll有很很大差異,差異如下:

a、epoll使用一組函數來完成任務,而不是單個函數;
b、epoll把用戶關心的文件描述符上的事件放在內核裏的一個事件表中,從而無須像select和poll那樣每次調用都要重複傳入文件描述符集或事件集。但epoll需要使用一個額外的文件描述符,來唯一標識內核中的這個事件表。這個文件描述符使用epoll_create函數來出創建。
c、epoll通過epoll_create創建一個用於epoll輪詢的描述符,通過epoll_ctl添加/修改/刪除事件,通過epoll_wait檢查事件,epoll_wait的第二個參數用於存放結果。
d、epoll與select、poll不同,首先,其不用每次調用都向內核拷貝事件描述信息,在第一次調用後,事件信息就會與對應的epoll描述符關聯起來。另外epoll不是通過輪詢,而是通過在等待的描述符上註冊回調函數,當事件發生時,回調函數負責把發生的事件存儲在就緒事件鏈表中,最後寫到用戶空間。
e、epoll返回後,該參數指向的緩衝區中即爲發生的事件,對緩衝區中每個元素進行處理即可,而不需要像poll、select那樣進行輪詢檢查。
系統調用:

#include <sys/epoll.h>

1、創建用戶關心的文件描述符:
int epoll_create(int size)	//創建額外的文件描述符。size用來告訴內核事件表需要多大。返回的文件描述符將用作其他所有epoll系統調用的第一個參數,以指定要訪問的內核事件表。

2、內核事件表相關接口:
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)	//fd參數是要操作的文件描述符,op參數則指定操作類型,操作類型有3種(EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL),event參數指定事件,它是epoll_event結構指針類型。

3、epoll_wait函數
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout)	
/* 
epoll系列系統調用的主要接口,它在一段超時時間內等待一組文件描述符上的事件。該函數成功時返回就緒的文件描述符的個數,失敗時返回-1並設置errno;
epoll_wait函數如果檢測到事件,就將所有就緒的事件從內核事件表(由epfd參數指定)中複製到它的第二個參數events指向的數組中。這個數組只用於輸出
epoll_wait檢測到的就緒事件,而不像select和poll的數組參數那樣既用於傳入用戶註冊的事件,又用於輸出內核檢測到的就緒事件。這就極大地提高了應用程序索引就緒文件描述符的效率。
*/

4、三組I/O複用函數的對比

系統調用 select poll epoll
事件集合 用戶通過3個參數分別傳入感興趣的可讀、可寫及異常等事件,內核通過對這些參數的在線修改來反饋其中的就緒事件。這使得用戶每次調用select都要重置這3個參數 統一處理所有事件類型,因此只需一個事件集參數。用戶通過pollfd.events傳入感興趣的事件,內核通過修改pollfd.revents反饋其中就緒的事件 內核通過一個事件表直接管理用戶感興趣的所有事件。因此每次調用epoll_wait時,無須反覆傳入用戶感興趣的事件。epoll_wait系統調用的參數events僅用來反饋就緒的事件
應用程序索引就緒文件描述符的時間複雜度 O(n) O(n) O(1)
最大支持文件描述符數 一般有最大值限制(1024) 65535 65535
工作模式 LT(Level Triger-電平出發) LT(Level Triger-電平出發) LT、同時支持ET高效模式
內核實現和工作效率 採用輪詢方式來檢測就緒事件,算法時間複雜度爲O(n) 採用輪詢方式來檢測就緒事件,算法時間複雜度爲O(n) 採用回調方式來檢測就緒事件,算法時間複雜度爲O(1)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章