2020.05.15
三種常見I/O複用函數比較
#include<sys/select.h>
int select(int fds, fd_set *readfds, fd_set *writefds, fd_set *exceptionfds, struct timeval *temiout);
//nfds指定被監聽的文件描述符總數,通常被設置爲select監聽的文件描述符的最大值+1;
//readfds,writefds,exceptfds分別指可讀、可寫、異常等事件對應的文件描述符集合,通過這三個參數傳入感興趣的文件描述符,select返回時,內核將修改它們來通知應用程序那些文件描述符已就緒
#include<poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int temeout);
struct pollfd{
int fd; //文件描述符
short events; //註冊時間
short revents; //實際發生的事件,由內核填充
};
//fds指定感興趣的文件描述符上發生的可讀、可寫、異常事件
//nfds指定被監聽事件集合fds的大小
#include<sys/epoll.h>
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
struct epoll_event{
__uint32_t events;
epoll_data_t data;
};
typedef union epoll_data{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
}epoll_data_t;
//根據參數op,對epfd所標識的epoll時間表進行事件註冊、修改、刪除
int epoll_wait(int eplfd, struct epoll_event *events, int maxevents, int timeout);
- 事件集對比
- select並沒有將事件與文件描述符綁定,因此select需要三個fd_set類型的參數來分別傳入和輸出可讀、可寫、異常事件,這使得select不能處理更多類型的事件;另一方面由於內核直接對fd_set進行修改,應用程序在下次調用select前不得不重置fd_set;
- poll的參數pollfd同時把文件描述符和事件定義在其中,使得任何事件被統一處理;而且內核修改的是revents成員,是的應用程序不需重置事件集合;
- epoll不同於select和poll,epoll通過在內核註冊一個事件表,通過獨立函數epoll_ctl來管理事件表,每次epoll_wait調用都將直接從內核事件表中取得用戶註冊的事件,而無需反覆的從用戶空間讀入這些事件。
由於每次select和poll調用都返回整個用戶註冊的事件集合,所以每次應用程序索引就緒文件描述符的時間複雜度都爲O(n),而epoll_wait調用的events參數僅用來返回就緒的事件,使得應用程序索引就緒文件描述符的複雜度達到O(1)。
- 最大支持文件描述符數
- select允許監聽的最大文件描述符有限,雖然用戶可以修改,但是會導致不可預期的結果;
- poll和epoll分別使用nfds和maxevents指定最多監聽多少個文件描述符和事件,兩個參數的最大值都是65535,16位。
-
工作模式
select和poll只支持相對低效的LT模式,而epoll支持ET模式,而且epoll還支持EPOLLONESHOT事件,可以進一步減少可讀、可寫、異常等時間被觸發的次數; -
實現原理
- select和poll都採用輪詢的方式,每次調用都要掃描整個註冊文件描述符集合,並將其中就緒的返回給用戶程序,因此其檢測就緒事件的複雜度是O(n);
- epoll採用回調的方式,內核檢測到就緒的文件描述符時,將觸發回調函數,回調函數就將該文件描述符上對應的事件插入內核就緒事件隊列。內核最後在適當的時機將就緒隊列中的內容拷貝到用戶空間,而epoll_wait無須輪詢整個文件描述符集合來檢測那些事件已經就緒,其時間複雜度是O(1)。
但是當活動鏈接較多時,epoll_wait的效率不一定比select和poll高,因爲此時回調函數被觸發得過於頻繁,所以epoll_wait適用於;連接數量多,但活動鏈接數量少的情況。