epoll机制浅析Word格式文档下载.docx
《epoll机制浅析Word格式文档下载.docx》由会员分享,可在线阅读,更多相关《epoll机制浅析Word格式文档下载.docx(10页珍藏版)》请在冰点文库上搜索。
intfd;
__uint32_tu32;
__uint64_tu64;
}epoll_data_t;
structepoll_event{
__uint32_tevents;
/*Epollevents*/
epoll_data_tdata;
/*Userdatavariable*/
};
events可以是以下几个宏的集合:
EPOLLIN:
表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:
表示对应的文件描述符可以写;
EPOLLPRI:
表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:
表示对应的文件描述符发生错误;
EPOLLHUP:
表示对应的文件描述符被挂断;
EPOLLET:
将EPOLL设为边缘触发(EdgeTriggered)模式,这是相对于水平触发(LevelTriggered)来说的。
EPOLLONESHOT:
只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
intepoll_wait(intepfd,structepoll_event*events,intmaxevents,inttimeout);
等待事件的产生,类似于select()调用。
参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。
该函数返回需要处理的事件数目,如返回0表示已超时。
2工作模式
EPOLL有两种模型:
LevelTriggered(LT)水平触发
EdgeTriggered(ET)边沿触发
水平触发是缺省的工作方式,并且同时支持block和no-blocksocket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。
如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错可能性要小一点。
传统的select/poll都是这种模型的代表.
边沿触发是高速工作方式,只支持no-blocksocket。
在这种模式下,当描述符从”未就绪”变为”就绪”时,内核通过epoll告诉你,然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK错误)。
但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(onlyonce)。
比如有这样一个例子:
1.我们已经把一个用来从管道中读取数据的文件句柄(RFD)添加到epoll描述符
2.这个时候从管道的另一端被写入了2KB的数据
3.调用epoll_wait
(2),并且它会返回RFD,说明它已经准备好读取操作
4.然后我们读取了1KB的数据
5.调用epoll_wait
(2)......
LT工作模式
以LT方式调用epoll接口的时候,它就相当于一个速度比较快的poll
(2),并且无论后面的数据是否被使用,因此他们具有同样的职能。
因为即使使用ET模式的epoll,在收到多个chunk的数据的时候仍然会产生多个事件。
调用者可以设定EPOLLONESHOT标志,在epoll_wait
(2)收到事件后,epoll会与事件关联的文件句柄从epoll描述符中禁止掉。
因此当EPOLLONESHOT设定后,使用带有EPOLL_CTL_MOD标志的epoll_ctl
(2)处理文件句柄就成为调用者必须做的事情。
ET工作模式
如果我们在第1步将RFD添加到epoll描述符的时候使用了EPOLLET标志,那么在第5步调用epoll_wait
(2)之后将有可能会挂起,因为剩余的数据还存在于文件的输入缓冲区内,而且数据发出端还在等待一个针对已经发出数据的反馈信息。
只有在监视的文件句柄上发生了某个事件的时候ET工作模式才会汇报事件。
因此在第5步的时候,调用者可能会放弃等待仍在存在于文件输入缓冲区内的剩余数据。
在上面的例子中,会有一个事件产生在RFD句柄上,因为在第2步执行了一个写操作,然后,事件将会在第3步被销毁。
因为第4步的读取操作没有读空文件输入缓冲区内的数据,因此我们在第5步调用epoll_wait
(2)完成后,是否挂起是不确定的。
epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。
最好以下面的方式调用ET模式的epoll接口:
i基于非阻塞文件句柄
ii只有当read
(2)或者write
(2)返回EAGAIN时才需要挂起,等待。
但这并不是说每次read()时都需要循环读,直到读到产生一个EAGAIN才认为此次事件处理完成,当read()返回的读到的数据长度小于请求的数据长度时,就可以确定此时缓冲中已没有数据了,也就可以认为此事读事件已处理完成。
3使用实例
那么究竟如何来使用epoll呢?
其实,通过在包含一个头文件#include<
sys/epoll.h>
以及几个简单的API将可以大大的提高你的网络服务器的支持人数。
首先通过create_epoll(intmaxfds)来创建一个epoll的句柄,其中maxfds是你epoll所支持的最大句柄数。
这个函数会返回一个新的epoll句柄,之后的所有操作将通过这个句柄来进行操作。
在用完之后,记得用close()来关闭这个创建出来的epoll句柄。
之后在网络主循环里面,每一帧的调用epoll_wait(intepfd,epoll_eventevents,intmaxevents,inttimeout)来查询所有的网络接口,看哪一个可以读,哪一个可以写了。
几乎所有的epoll程序都使用下面的框架:
for(;
;
)
{
nfds=epoll_wait(epfd,events,20,500);
for(i=0;
i<
nfds;
++i)
if(listenfd==events[i].data.fd)//有新的连接
connfd=accept(listenfd,
(sockaddr*)&
clientaddr,
&
clilen);
=connfd;
=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&
ev);
}
elseif(events[i].events&
EPOLLIN)
n=read(sockfd,line,MAXLINE))<
0
ev.data.ptr=md;
//md为自定义类型,添加数据
=EPOLLOUT|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&
//修改标识符,等待下一个循环时发送数据,异步处理的精髓
elseif(events[i].events&
EPOLLOUT)
structmyepoll_data*md=
(myepoll_data*)events[i].data.ptr;
//取数据
sockfd=md->
fd;
send(sockfd,md->
ptr,strlen((char*)md->
ptr),0);
ev.data.fd=sockfd;
ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&
//修改标识符,等待下一个循环时接收数据
else
//其他的处理
下面给出一个完整的服务器端例子:
#include<
sys/socket.h>
netinet/in.h>
arpa/inet.h>
fcntl.h>
unistd.h>
stdio.h>
errno.h>
#defineMAXLINE5
#defineOPEN_MAX100
#defineLISTENQ20
#defineSERV_PORT5000
#defineINFTIM1000
voidsetnonblocking(intsock)
{
intopts;
opts=fcntl(sock,F_GETFL);
if(opts<
0)
perror("
fcntl(sock,GETFL)"
);
exit
(1);
opts=opts|O_NONBLOCK;
if(fcntl(sock,F_SETFL,opts)<
0)
fcntl(sock,SETFL,opts)"
}
intmain(intargc,char*argv[])
inti,maxi,listenfd,connfd,sockfd,epfd,nfds,portnumber;
ssize_tn;
charline[MAXLINE];
socklen_tclilen;
if(2==argc)
if((portnumber=atoi(argv[1]))<
0)
fprintf(stderr,"
Usage:
%sportnumber\a\n"
argv[0]);
return1;
fprintf(stderr,"
//声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件
structepoll_eventev,events[20];
//生成用于处理accept的epoll专用的文件描述符
epfd=epoll_create(256);
structsockaddr_inclientaddr;
structsockaddr_inserveraddr;
listenfd=socket(AF_INET,SOCK_STREAM,0);
//把socket设置为非阻塞方式
//setnonblocking(listenfd);
//设置与要处理的事件相关的文件描述符
=listenfd;
//设置要处理的事件类型
=EPOLLIN;
//注册epoll事件
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&
bzero(&
serveraddr,sizeof(serveraddr));
serveraddr.sin_family=AF_INET;
char*local_addr="
127.0.0.1"
;
inet_aton(local_addr,&
(serveraddr.sin_addr));
=htons(portnumber);
bind(listenfd,(sockaddr*)&
listen(listenfd,LISTENQ);
maxi=0;
for(;
//等待epoll事件的发生
//处理所发生的所有事件
if(listenfd==events[i].data.fd)//如果新监测到一个SOCKET用户连接到了绑定的SOCKET端口,建立新的连接。
connfd=
accept(listenfd,(sockaddr*)&
clientaddr,&
if(connfd<
connfd<
0"
//setnonblocking(connfd);
char*str=inet_ntoa(clientaddr.sin_addr);
printf("
accaptaconnectionfrom%s\n"
str);
//设置用于读操作的文件描述符
//设置用于注测的读操作事件
=EPOLLIN;
//注册ev
EPOLLIN)//如果是已经连接的用户,并且收到数据,那么进行读入。
if((sockfd=events[i].data.fd)<
continue;
if((n=read(sockfd,line,MAXLINE))<
if(errno==ECONNRESET)
close(sockfd);
events[i].data.fd=-1;
readlineerror\n"
elseif(n==0)
line[n]='
\0'
prinf("
read%s\n"
line);
//设置用于写操作的文件描述符
=sockfd;
//设置用于注册的写操作事件
=EPOLLOUT|EPOLLET;
//修改sockfd上要处理的事件为EPOLLOUT
//epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&
EPOLLOUT)//如果有数据发送
sockfd=events[i].data.fd;
write(sockfd,line,n);
//设置用于注册的读操作事件
//修改sockfd上要处理的事件为EPOLIN
return0;
客户端直接连接到这个服务器就好了。
。
来自: