在Linux Socket服務(wù)器短編程時(shí),為了處理大量客戶的連接請(qǐ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來實(shí)現(xiàn)I/O復(fù)用支持高并發(fā),目前在高并 發(fā)的場(chǎng)景下,nginx越來越收到歡迎。這里有個(gè)文章參考。Nginx成為全球Top1000網(wǎng)站最受歡迎的Web服務(wù)器。 據(jù) w3techs 7月 3 日的統(tǒng)計(jì)數(shù)據(jù)表明,在全球 Top 1000 的網(wǎng)站中,有 34.9% 的網(wǎng)站在使用 Nginx,這使得 Nginx 超越了 Apache,成為了高流量網(wǎng)站最信任的 Web 服務(wù)器。下圖是統(tǒng)計(jì)數(shù)據(jù)。 select:下面是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ù)會(huì)阻塞,直到有描述副就緒(有數(shù)據(jù) 可讀、可寫、或者有except),或者超時(shí)(timeout指定等待時(shí)間,如果立即返回設(shè)為null即可),函數(shù)返回。當(dāng)select函數(shù)返回后,可以 通過遍歷fdset,來找到就緒的描述符。 select目前幾乎在所有的平臺(tái)上支持,其良好跨平臺(tái)支持也是它的一個(gè)優(yōu)點(diǎn)。select的一 個(gè)缺點(diǎn)在于單個(gè)進(jìn)程能夠監(jiān)視的文件描述符的數(shù)量存在最大限制,在Linux上一般為1024,可以通過修改宏定義甚至重新編譯內(nèi)核的方式提升這一限制,但 是這樣也會(huì)造成效率的降低。 poll:int poll (struct pollfd *fds, unsigned int nfds, int timeout);
不同與select使用三個(gè)位圖來表示三個(gè)fdset的方式,poll使用一個(gè) pollfd的指針實(shí)現(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ù)-值”傳遞的方式。同時(shí),pollfd并沒有最大數(shù)量限制(但是數(shù)量過大后性能也是會(huì)下降)。 和select函數(shù)一樣,poll返回后,需要輪詢pollfd來獲取就緒的描述符。 從上面看,select和poll都需要在返回后,通過遍歷文件描述符來獲取已經(jīng)就緒的socket。事實(shí)上,同時(shí)連接的大量客戶端在一時(shí)刻可能只有很少的處于就緒狀態(tài),因此隨著監(jiān)視的描述符數(shù)量的增長,其效率也會(huì)線性下降。 epoll: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三個(gè)函數(shù)。epoll_create函數(shù)創(chuàng)建epoll文件描述符,參數(shù)size并不是限制了epoll所能監(jiān)聽的描述符最大個(gè)數(shù),只是對(duì)內(nèi)核初始分配內(nèi)部數(shù)據(jù)結(jié)構(gòu)的一個(gè)建議。返回是epoll描述符。-1表示創(chuàng)建失敗。epoll_ctl 控制對(duì)指定描述符fd執(zhí)行op操作,event是與fd關(guān)聯(lián)的監(jiān)聽事件。op操作有三種:添加EPOLL_CTL_ADD,刪除EPOLL_CTL_DEL,修改EPOLL_CTL_MOD。分別添加、刪除和修改對(duì)fd的監(jiān)聽事件。epoll_wait 等待epfd上的io事件,最多返回maxevents個(gè)事件。 在 select/poll中,進(jìn)程只有在調(diào)用一定的方法后,內(nèi)核才對(duì)所有監(jiān)視的文件描述符進(jìn)行掃描,而epoll事先通過epoll_ctl()來注冊(cè)一 個(gè)文件描述符,一旦基于某個(gè)文件描述符就緒時(shí),內(nèi)核會(huì)采用類似callback的回調(diào)機(jī)制,迅速激活這個(gè)文件描述符,當(dāng)進(jìn)程調(diào)用epoll_wait() 時(shí)便得到通知。 epoll的優(yōu)點(diǎn)主要是一下幾個(gè)方面: 1. 監(jiān)視的描述符數(shù)量不受限制,它所支持的FD上限是最大可以打開文件的數(shù)目,這個(gè)數(shù)字一般遠(yuǎn)大于2048,舉個(gè)例子,在1GB內(nèi)存的機(jī)器上大約是10萬左 右,具體數(shù)目可以cat /proc/sys/fs/file-max察看,一般來說這個(gè)數(shù)目和系統(tǒng)內(nèi)存關(guān)系很大。select的最大缺點(diǎn)就是進(jìn)程打開的fd是有數(shù)量限制的。這對(duì) 于連接數(shù)量比較大的服務(wù)器來說根本不能滿足。雖然也可以選擇多進(jìn)程的解決方案( Apache就是這樣實(shí)現(xiàn)的),不過雖然linux上面創(chuàng)建進(jìn)程的代價(jià)比較小,但仍舊是不可忽視的,加上進(jìn)程間數(shù)據(jù)同步遠(yuǎn)比不上線程間同步的高效,所以也 不是一種完美的方案。 2. IO的效率不會(huì)隨著監(jiān)視fd的數(shù)量的增長而下降。epoll不同于select和poll輪詢的方式,而是通過每個(gè)fd定義的回調(diào)函數(shù)來實(shí)現(xiàn)的。只有就緒的fd才會(huì)執(zhí)行回調(diào)函數(shù)。 3.支持電平觸發(fā)和邊沿觸發(fā)(只告訴進(jìn)程哪些文件描述符剛剛變?yōu)榫途w狀態(tài),它只說一遍,如果我們沒有采取行動(dòng),那么它將不會(huì)再次告知,這種方式稱為邊緣觸發(fā))兩種方式,理論上邊緣觸發(fā)的性能要更高一些,但是代碼實(shí)現(xiàn)相當(dāng)復(fù)雜。 4.mmap加速內(nèi)核與用戶空間的信息傳遞。epoll是通過內(nèi)核于用戶空間mmap同一塊內(nèi)存,避免了無畏的內(nèi)存拷貝。
|
|