http://blog.csdn.net/chenxun_2010/article/details/50493481
1、epoll是什么?
epoll是當(dāng)前在Linux下開發(fā)大規(guī)模并發(fā)網(wǎng)絡(luò)程序的熱門人選,epoll?在Linux2.6內(nèi)核中正式引入,和select相似,都是I/O多路復(fù)用(IO multiplexing)技術(shù)。
Linux下設(shè)計(jì)并發(fā)網(wǎng)絡(luò)程序,常用的模型有:
? ? ??Apache模型(Process Per Connection,簡(jiǎn)稱PPC)
? ? ?TPC(Thread PerConnection)模型
? ? ?select模型和poll模型。
? ? ?epoll模型
2、常用模型的缺點(diǎn)
???PPC/TPC模型
? ? ? ?這兩種模型思想類似,就是讓每一個(gè)到來(lái)的連接都有一個(gè)進(jìn)程/線程來(lái)服務(wù)。這種模型的代價(jià)是它要時(shí)間和空間。連接較多時(shí),進(jìn)程/線程切換的開銷比較大。因此這類模型能接受的最大連接數(shù)都不會(huì)高,一般在幾百個(gè)左右。
? ?select模型
? ? ? ? ??最大并發(fā)數(shù)限制:因?yàn)橐粋€(gè)進(jìn)程所打開的fd(文件描述符)是有限制的,由FD_SETSIZE設(shè)置,默認(rèn)值是1024/2048,因此select模型的最大并發(fā)數(shù)就被相應(yīng)限制了。
? ? ? ? ? 效率問(wèn)題:select每次調(diào)用都會(huì)線性掃描全部的fd集合,這樣效率就會(huì)呈現(xiàn)線性下降,把FD_SETSIZE改大可能造成這些fd都超時(shí)了。
? ? ? ? ??內(nèi)核/用戶空間內(nèi)存拷貝問(wèn)題:如何讓內(nèi)核把fd消息通知給用戶空間呢?在這個(gè)問(wèn)題上select采取了內(nèi)存拷貝方法。
? ??poll模型
? ? ? ? ?基本上效率和select是相同的,select缺點(diǎn)的2和3它都沒(méi)有改掉。
? ??epoll的改進(jìn)
對(duì)比其他模型的問(wèn)題,epoll的改進(jìn)如下:
? ? ? ?epoll沒(méi)有最大并發(fā)連接的限制,上限是最大可以打開文件的數(shù)目,這個(gè)數(shù)字一般遠(yuǎn)大于2048,?一般來(lái)說(shuō)這個(gè)數(shù)目和系統(tǒng)內(nèi)存關(guān)系很大,具體數(shù)目可以cat /proc/sys/fs/file-max察看。
? ? ? ?效率提升,Epoll最大的優(yōu)點(diǎn)就在于它只管你“活躍”的連接,而跟連接總數(shù)無(wú)關(guān),因此在實(shí)際的網(wǎng)絡(luò)環(huán)境中,Epoll的效率就會(huì)遠(yuǎn)遠(yuǎn)高于select和poll。
? ? ? ?內(nèi)存拷貝,Epoll在這點(diǎn)上使用了“共享內(nèi)存”,這個(gè)內(nèi)存拷貝也省略了。
?
3、?epoll為什么高效
epoll的高效和其數(shù)據(jù)結(jié)構(gòu)的設(shè)計(jì)是密不可分的。
首先回憶一下select模型,當(dāng)有I/O事件到來(lái)時(shí),select通知應(yīng)用程序有事件到了,應(yīng)用程序必須輪詢所有的fd集合,測(cè)試每個(gè)fd是否有事件發(fā)生,并處理事件;代碼像下面這樣:
int? res = select(maxfd+1, &readfds, NULL, NULL, 120);
if?(res > 0)
{
????for?(int?i = 0; i < MAX_CONNECTION; i++)
????{
????????if?(FD_ISSET(allConnection[i],&readfds))
????????{
????????????handleEvent(allConnection[i]);
????????}
????}
}
// if(res == 0) handle timeout, res < 0 handle error
?
epoll不僅會(huì)告訴應(yīng)用程序有I/0事件到來(lái),還會(huì)告訴應(yīng)用程序相關(guān)的信息,這些信息是應(yīng)用程序填充的,因此根據(jù)這些信息應(yīng)用程序就能直接定位到事件,而不必遍歷整個(gè)fd集合。
int?res = epoll_wait(epfd, events, 20, 120);
for?(int?i = 0; i < res;i++)
{
????handleEvent(events[n]);
}
?epoll關(guān)鍵數(shù)據(jù)結(jié)構(gòu)
前面提到epoll速度快和其數(shù)據(jù)結(jié)構(gòu)密不可分,其關(guān)鍵數(shù)據(jù)結(jié)構(gòu)就是:
struct ?epoll_event?{
???__uint32_t??????events;??//epoll events
???epoll_data_t???data;???//user data variable
};
?
typedef ?union?epoll_data?{
???void*???ptr;
???int??????fd;
???__uint32_t??u32;
???__uint64_t??u64;
}epoll_data_t;
可見(jiàn)epoll_data是一個(gè)union結(jié)構(gòu)體,借助于它應(yīng)用程序可以保存很多類型的信息:fd、指針等等。有了它,應(yīng)用程序就可以直接定位目標(biāo)了。
?
使用epoll
epoll的API:
int??epoll_create(int? size);
int??epoll_ctl(int?epfd,?int?op,?int?fd,?structepoll_event?*event);
int??epoll_wait(int?epfd,?struct?epoll_event* events,?int?maxevents.?int?timeout);
int??epoll_create(int?size);
創(chuàng)建一個(gè)epoll的文件描述符,參數(shù)size告訴內(nèi)核這個(gè)監(jiān)聽的數(shù)目共有多大。
??int??epoll_ctl(int?epfd,?int?op,?int?fd,?structepoll_event?*event);
? ? ?epoll的事件注冊(cè)函數(shù)。
? ? ?參數(shù)epfd是epoll_create返回值。
? ? 參數(shù)op為
? ? ? ? ? EPOLL_CTL_ADD?注冊(cè)新的fd到epfd中
? ? ? ? ? EPOLL_CTL_MOD?修改已經(jīng)注冊(cè)的fd的監(jiān)聽事件
? ? ? ? ? EPOLL_CTL_DEL?從epfd中刪除一個(gè)fd
參數(shù)fd是需要監(jiān)聽文件描述符。
參數(shù)event是告訴內(nèi)核需要監(jiān)聽什么事件。event->events的不同的值表示對(duì)應(yīng)的文件描述符的不同事件:
???EPOLLIN??可以讀(包括對(duì)端Socket正常關(guān)閉)
???EPOLLOUT?可以寫
???EPOLLPRI有緊急的數(shù)據(jù)可讀(有帶外數(shù)據(jù)OOB到來(lái),TCP中的URG包)
???EPOLLERR該文件描述符發(fā)生錯(cuò)誤
???EPOLLHUP該文件描述符被掛斷
???EPOLLET?????將epoll設(shè)置為邊緣觸發(fā)(Edge Triggered)模式。
???EPOLLONESHOT只監(jiān)聽一次事件,監(jiān)聽完之后,如果還想監(jiān)聽需要再次把該文件描述符加入到epoll隊(duì)列中
?
int??epoll_wait(int?epfd,?struct?epoll_event* events,?int?maxevents.?int?timeout);
等待事件的產(chǎn)生。
參數(shù)events用來(lái)從內(nèi)核得到事件的集合
參數(shù)maxevents告之內(nèi)核這個(gè)events有多大(maxevents不能大于size)
參數(shù)timeout是超時(shí)時(shí)間(毫秒)
?
epoll的模式:
? ? ? ?LT模式:Level Triggered水平觸發(fā)
? ? ? ? ? ? 這個(gè)是缺省的工作模式。同時(shí)支持block socket和non-block socket。內(nèi)核會(huì)告訴程序員一個(gè)文件描述符是否就緒了。如果程序員不作任何操作,內(nèi)核仍會(huì)通知。
? ? ? ?ET模式:Edge Triggered?邊緣觸發(fā)
? ? ? ? ? ? ? ? 是一種高速模式。僅當(dāng)狀態(tài)發(fā)生變化的時(shí)候才獲得通知。這種模式假定程序員在收到一次通知后能夠完整地處理事件,于是內(nèi)核不再通知這一事件。注意:緩沖區(qū)中還 ? ? ? ? ?有未處理的數(shù)據(jù)不算狀態(tài)變化,所以ET模式下程序員只讀取了一部分?jǐn)?shù)據(jù)就再也得不到通知了,正確的用法是程序員自己確認(rèn)讀完了所有的字節(jié)(一直調(diào)用read/write直到 ? ? ? ? ?出錯(cuò)EAGAIN為止)。
?
?
一個(gè)例子:
[cpp]?view plaincopy
#include?<netdb.h>????#include?<sys/socket.h>????#include?<sys/epoll.h>????#include?<netinet/in.h>????#include?<arpa/inet.h>????#include?<fcntl.h>????#include?<unistd.h>????#include?<stdio.h>????#include?<string.h>????#include?<stdlib.h>????#include?<errno.h>?????????????static?int??create_and_bind?(char?*port){????????struct??addrinfo?hints;????????struct??addrinfo?*result,?*rp;????????int??s,?sfd;????????memset?(&hints,?0,?sizeof?(struct?addrinfo));????????hints.ai_family?=?AF_UNSPEC;?????????????hints.ai_socktype?=?SOCK_STREAM;?????????hints.ai_flags?=?AI_PASSIVE;?????????????s?=?getaddrinfo?(NULL,?port,?&hints,?&result);????????if?(s?!=?0){????????????fprintf?(stderr,?"getaddrinfo:?%s\n",?gai_strerror?(s));????????????return?-1;????????}????????for?(rp?=?result;?rp?!=?NULL;?rp?=?rp->ai_next){????????????sfd?=?socket?(rp->ai_family,?rp->ai_socktype,?rp->ai_protocol);?????????????if?(sfd?==?-1)????????????????continue;????????????s?=?bind?(sfd,?rp->ai_addr,?rp->ai_addrlen);?????????????if?(s?==?0)????????????{????????????????????????????????break;????????????}????????????close?(sfd);????????}????????if?(rp?==?NULL){????????????fprintf?(stderr,?"Could?not?bind\n");????????????return?-1;????????}????????freeaddrinfo?(result);????????return?sfd;????}???????????????????static??int???make_socket_non_blocking?(int?sfd)?{?????????????int?flags,?s;????????flags?=?fcntl?(sfd,?F_GETFL,?0);????????if?(flags?==?-1){????????????perror?("fcntl");????????????return?-1;????????}????????flags?|=?O_NONBLOCK;????????s?=?fcntl?(sfd,?F_SETFL,?flags);????????if?(s?==?-1){????????????perror?("fcntl");????????????return?-1;????????}????????return?0;????}??????????????#define??MAXEVENTS?64?????????????????int??main?(int?argc,?char?*argv[])?{????????int?sfd,?s;????????int?efd;????????struct??epoll_event?event;????????struct??epoll_event?*events;????????if?(argc?!=?2)?{????????????fprintf?(stderr,?"Usage:?%s?[port]\n",?argv[0]);????????????exit?(EXIT_FAILURE);????????}????????sfd?=?create_and_bind?(argv[1]);??????????????s?=?make_socket_non_blocking?(sfd);????????s?=?listen?(sfd,?SOMAXCONN);????????efd?=?epoll_create1?(0);????????event.data.fd?=?sfd;????????event.events?=?EPOLLIN?|?EPOLLET;????????s?=?epoll_ctl?(efd,?EPOLL_CTL_ADD,?sfd,?&event);????????????????events?=?(struct??epoll_event*)calloc?(MAXEVENTS,?sizeof?event);????????????????while?(1)?{????????????int?n,?i;????????????n?=?epoll_wait?(efd,?events,?MAXEVENTS,?-1);????????????for?(i?=?0;?i?<?n;?i++)?{????????????????if?((events[i].events?&?EPOLLERR)?||??(events[i].events?&?EPOLLHUP)?||?(!(events[i].events?&?EPOLLIN)))?{??????????????????????????????????????fprintf?(stderr,?"epoll?error\n");????????????????????close?(events[i].data.fd);????????????????????continue;????????????????}else?if?(sfd?==?events[i].data.fd)?{??????????????????????????????????????????printf("Incoming?connection?!\n");????????????????????while?(1)?{????????????????????????struct?sockaddr?in_addr;????????????????????????socklen_t?in_len;????????????????????????int?infd;????????????????????????char?hbuf[NI_MAXHOST],?sbuf[NI_MAXSERV];????????????????????????in_len?=?sizeof?in_addr;????????????????????????infd?=?accept?(sfd,?&in_addr,?&in_len);?????????????????????????if?(infd?==?-1)?{????????????????????????????if?((errno?==?EAGAIN)?||?(errno?==?EWOULDBLOCK))?{??????????????????????????????????????????????????????????????????break;????????????????????????????}?else??{????????????????????????????????perror?("accept");????????????????????????????????break;????????????????????????????}????????????????????????}????????????????????????s?=?getnameinfo?(&in_addr,?in_len,?hbuf,?sizeof?hbuf,?sbuf,?sizeof?sbuf,?NI_NUMERICHOST?|?NI_NUMERICSERV);????????????????????????if?(s?==?0)?{????????????????????????????printf("Accepted?connection?on?descriptor?%d?(host=%s,?port=%s)\n",?infd,?hbuf,?sbuf);????????????????????????}????????????????????????s?=?make_socket_non_blocking?(infd);??????????????????????????event.data.fd?=?infd;??????????????????????????event.events?=?EPOLLIN?|?EPOLLET;??????????????????????????s?=?epoll_ctl?(efd,?EPOLL_CTL_ADD,?infd,?&event);????????????????????}????????????????????continue;????????????????}?else???????????????{????????????????????????????????????????int?conn?=?events[i].data.fd;??????????????????if?(conn?<?0)??????????????????????continue;????????????????????char?recvbuf[1024]?=?{0};??????????????????int?ret?=?read(conn,?recvbuf,?1024);??????????????????if?(ret?==?-1)????????????????????????????????????????if?(ret?==?0)??????????????????{??????????????????????printf("client?close\n");??????????????????????close(conn);????????????????????????event?=?events[i];??????????????????????epoll_ctl(efd,?EPOLL_CTL_DEL,?conn,?&event);????????????????????????????????????????}????????????????????fputs(recvbuf,?stdout);??????????????????write(conn,?recvbuf,?strlen(recvbuf));????????????????????????????????????}????????????}????????}????????free?(events);????????close?(sfd);???????????return?EXIT_SUCCESS;????}???????
總結(jié)
以上是生活随笔為你收集整理的epoll用法整理 实现回声服务端的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。