Linux Socket 編程領(lǐng)域,為了處理大量連接請求場景,需要使用非阻塞 I/O 和復(fù)用。select、poll 和 epoll 是 Linux API 提供的 I/O 復(fù)用方式,自從 Linux 2.6 中加入了 epoll 之后,高性能服務(wù)器領(lǐng)域得到廣泛的應(yīng)用,現(xiàn)在比較出名的 Nginx 就是使用 epoll 來實現(xiàn) I/O 復(fù)用支持高并發(fā),目前在高并發(fā)的場景下,Nginx 越來越收到歡迎。
據(jù) w3techs 在 2015 年 8 月 10 日的統(tǒng)計數(shù)據(jù)表明,在全球 Top 1000 的網(wǎng)站中,有 43.7% 的網(wǎng)站在使用 Nginx,這使得 Nginx 超越了 Apache,成為了高流量網(wǎng)站最信任的 Web 服務(wù)器足足有兩年時間。已經(jīng)確定在使用 Nginx 的站點有:Wikipedia,WordPress,Reddit,Tumblr,Pinterest,Dropbox,Slideshare,Stackexchange 等,可以持續(xù)羅列好幾個小時,他們太多了。
下圖是統(tǒng)計數(shù)據(jù):
http://wiki.jikexueyuan.com/project/openresty/images/nginx.png" alt="" />
下面是 select 函數(shù)接口:
int select (int n, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
select 函數(shù)監(jiān)視的文件描述符分 3 類,分別是 writefds、readfds 和 exceptfds。調(diào)用后 select 函數(shù)會阻塞,直到有描述符就緒(有數(shù)據(jù) 可讀、可寫、或者有 except),或者超時(timeout 指定等待時間,如果立即返回設(shè)為 null 即可)。當(dāng) select 函數(shù)返回后,通過遍歷 fd_set,來找到就緒的描述符。
select 目前幾乎在所有的平臺上支持,其良好跨平臺支持是它的一大優(yōu)點。select 的一個缺點在于單個進程能夠監(jiān)視的文件描述符的數(shù)量存在最大限制,在 Linux 上一般為 1024,可以通過修改宏定義甚至重新編譯內(nèi)核的方式提升這一限制,但是這樣也會造成效率的降低。
int poll (struct pollfd *fds, unsigned int nfds, int timeout);
不同于 select 使用三個位圖來表示三個 fdset 的方式,poll 使用一個 pollfd 的指針實現(xiàn)。
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events to watch */
short revents; /* returned events witnessed */
};
pollfd 結(jié)構(gòu)包含了要監(jiān)視的 event 和發(fā)生的 event,不再使用 select “參數(shù)-值”傳遞的方式。同時,pollfd 并沒有最大數(shù)量限制(但是數(shù)量過大后性能也是會下降)。和 select 函數(shù)一樣,poll 返回后,需要輪詢 pollfd 來獲取就緒的描述符。
從上面看,select 和 poll 都需要在返回后,通過遍歷文件描述符來獲取已經(jīng)就緒的 socket。事實上,同時連接的大量客戶端在一時刻可能只有很少的處于就緒狀態(tài),因此隨著監(jiān)視的描述符數(shù)量的增長,其效率也會線性下降。
epoll 的接口如下:
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
int epoll_wait(int epfd, struct epoll_event * events,
int maxevents, int timeout);
主要是 epoll_create,epoll_ctl 和 epoll_wait 三個函數(shù)。epoll_create 函數(shù)創(chuàng)建 epoll 文件描述符,參數(shù) size 并不是限制了 epoll 所能監(jiān)聽的描述符最大個數(shù),只是對內(nèi)核初始分配內(nèi)部數(shù)據(jù)結(jié)構(gòu)的一個建議。epoll_ctl 完成對指定描述符 fd 執(zhí)行 op 操作控制,event 是與 fd 關(guān)聯(lián)的監(jiān)聽事件。op 操作有三種:添加 EPOLL_CTL_ADD,刪除 EPOLL_CTL_DEL,修改 EPOLL_CTL_MOD。分別添加、刪除和修改對 fd 的監(jiān)聽事件。epoll_wait 等待 epfd 上的 IO 事件,最多返回 maxevents 個事件。
在 select/poll 中,進程只有在調(diào)用一定的方法后,內(nèi)核才對所有監(jiān)視的文件描述符進行掃描,而 epoll 事先通過 epoll_ctl() 來注冊一個文件描述符,一旦基于某個文件描述符就緒時,內(nèi)核會采用類似 callback 的回調(diào)機制,迅速激活這個文件描述符,當(dāng)進程調(diào)用 epoll_wait 時便得到通知。
epoll 的優(yōu)點主要是一下幾個方面: