epoll机制浅析.docx

上传人:b****3 文档编号:7001853 上传时间:2023-05-10 格式:DOCX 页数:10 大小:20KB
下载 相关 举报
epoll机制浅析.docx_第1页
第1页 / 共10页
epoll机制浅析.docx_第2页
第2页 / 共10页
epoll机制浅析.docx_第3页
第3页 / 共10页
epoll机制浅析.docx_第4页
第4页 / 共10页
epoll机制浅析.docx_第5页
第5页 / 共10页
epoll机制浅析.docx_第6页
第6页 / 共10页
epoll机制浅析.docx_第7页
第7页 / 共10页
epoll机制浅析.docx_第8页
第8页 / 共10页
epoll机制浅析.docx_第9页
第9页 / 共10页
epoll机制浅析.docx_第10页
第10页 / 共10页
亲,该文档总共10页,全部预览完了,如果喜欢就下载吧!
下载资源
资源描述

epoll机制浅析.docx

《epoll机制浅析.docx》由会员分享,可在线阅读,更多相关《epoll机制浅析.docx(10页珍藏版)》请在冰点文库上搜索。

epoll机制浅析.docx

epoll机制浅析

epoll机制浅析

在linux的网络编程中,很长的时间都在使用select来做事件触发。

在linux新的内核中,有了一种替换它的机制,就是epoll。

相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。

因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。

并且,在头文件有这样的声明:

#define__FD_SETSIZE1024

表示select最多同时监听1024个fd,当然,可以通过修改头文件再重编译内核来扩大这个数目,但这似乎并不治本。

1基本函数

epoll的接口一共就三个函数:

intepoll_create(intsize);

创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。

这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。

需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

intepoll_ctl(intepfd,intop,intfd,structepoll_event*event);

epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。

第一个参数是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示:

EPOLL_CTL_ADD:

注册新的fd到epfd中;

EPOLL_CTL_MOD:

修改已经注册的fd的监听事件;

EPOLL_CTL_DEL:

从epfd中删除一个fd;

第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,structepoll_event结构如下:

typedefunionepoll_data{

void*ptr;

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以及几个简单的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

{

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,&ev);//修改标识符,等待下一个循环时发送数据,异步处理的精髓

}

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,&ev);//修改标识符,等待下一个循环时接收数据

}

else

{

//其他的处理

}

}

}

 

下面给出一个完整的服务器端例子:

#include

#include

#include

#include

#include

#include

#include

#include

#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)

{

perror("fcntl(sock,SETFL,opts)");

exit

(1);

}

}

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;

}

}

else

{

fprintf(stderr,"Usage:

%sportnumber\a\n",argv[0]);

return1;

}

//声明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|EPOLLET;

=EPOLLIN;

//注册epoll事件

epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);

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*)&serveraddr,sizeof(serveraddr));

listen(listenfd,LISTENQ);

maxi=0;

for(;;)

{

//等待epoll事件的发生

nfds=epoll_wait(epfd,events,20,500);

//处理所发生的所有事件

for(i=0;i

{

if(listenfd==events[i].data.fd)//如果新监测到一个SOCKET用户连接到了绑定的SOCKET端口,建立新的连接。

{

connfd=

accept(listenfd,(sockaddr*)&clientaddr,&clilen);

if(connfd<0)

{

perror("connfd<0");

exit

(1);

}

//setnonblocking(connfd);

char*str=inet_ntoa(clientaddr.sin_addr);

printf("accaptaconnectionfrom%s\n",str);

//设置用于读操作的文件描述符

=connfd;

//设置用于注测的读操作事件

=EPOLLIN|EPOLLET;

=EPOLLIN;

//注册ev

epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);

}

elseif(events[i].events&EPOLLIN)//如果是已经连接的用户,并且收到数据,那么进行读入。

{

if((sockfd=events[i].data.fd)<0)

continue;

if((n=read(sockfd,line,MAXLINE))<0)

{

if(errno==ECONNRESET)

{

close(sockfd);

events[i].data.fd=-1;

}

else

printf("readlineerror\n");

}

elseif(n==0)

{

close(sockfd);

events[i].data.fd=-1;

}

line[n]='\0';

prinf("read%s\n",line);

//设置用于写操作的文件描述符

=sockfd;

//设置用于注册的写操作事件

=EPOLLOUT|EPOLLET;

//修改sockfd上要处理的事件为EPOLLOUT

//epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);

}

elseif(events[i].events&EPOLLOUT)//如果有数据发送

{

sockfd=events[i].data.fd;

write(sockfd,line,n);

//设置用于读操作的文件描述符

=sockfd;

//设置用于注册的读操作事件

=EPOLLIN|EPOLLET;

//修改sockfd上要处理的事件为EPOLIN

epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);

}

}

}

return0;

}

 

客户端直接连接到这个服务器就好了。

来自:

 

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 小学教育 > 语文

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2