Event Poll epoll 详解,pollepoll
Event Poll epoll 详解,pollepoll
由于poll()和select()的局限,2.6内核引入了event poll(epoll)机制。虽然稍微复杂,但是epoll解决了它们共有的基本性能问题,并增加了一些新的特性。
poll()和select()每次调用都需要所有被监听的文件描述符。内核必须遍历所有被监视的文件描述符。当这个表变得很大时,成千上百的文件描述符,每次调用时的遍历就成为了明显的瓶颈。
1、创建一个新的epoll实例
使用epoll_create()或者epoll_cerate1()创建一个epoll上下文。这里epoll_cerate1()是epoll_cerate()的扩展版本。
#include <sys/epoll.h> int epoll_create (int size)调用成功后,epoll_create()创建一个epoll实例,返回与该实例关联的文件描述符。这个文件描述符和真正的文件没有关系,仅仅是为了后续调用使用epoll而创建的。size参数告诉内核需要监听的文件描述符数目,但不是最大值。传递一个适当的近似值会带来性能的提升,但不需要给出确切的数字。出错时,返回-1,设置errno为下列值之一:
EINVAL size不是正数
ENFILE 系统达到打开文件数的上限
ENOMEN 没有足够内存完成该次操作。
标准调用如下:
int epfd; epfd = epoll_create (100); if (epfd <0 ) perror("epoll_create");epoll_create返回的文件描述符需要用close()关闭。
2、控制 epoll
epoll_ctl 可以向指定的epoll上下文加入或删除文件描述符:
#include <sys/epoll.h> int epoll_ctl (int epfd, int op, int fd, struct epoll_event *event);头文件<sys/epoll.h>中定义了epoll event结构体
struct epoll_event { _u32 events; union { void * ptr; int fd; _u32 u32; _u64 u64; }data; };epoll_ctl()成功调用将关联epoll实例和epfd。参数op指定对fd要进行的操作。event参数描述epoll更具体的行为以下是参数op的有效值:EPOLL_CTL_ADD 把fd指定的文件添加到epfd指定的epoll实例监听集中,监听event中定义的事件。EPOLL_CTL_DEL 把fd指定的文件从epfd指定的epoll监听集中删除。EPOLL_CTL_MOD 使用event改变在已有fd上的监听行为。epoll_event结构体中的event参数列出了在给定文件描述符上监听的事件。多个事件可以使用位或运算同时指定。以下是有效值:EPOLLERR 文件出错。即使不设置这个标志,这个事件也是被监听的。EPOLLET 使用边沿触发。默认是水平触发。EPOLLHUP 文件被挂起。即使不设置这个标志,这个事件也是被监听的。EPOLLIN 文件未阻塞,可读。EPOLLONESHOT 在一次事件产生被处理之后,文件不在被监听。必须不再被监听。必须使用EPOLL_CTL_MOD指定新的事件,以便重新监听文件。EPOLLOUT 文件未阻塞,可写。EPOLLPRI 高优先级的带外数据可读。event_poll中的data字段由用户使用。确认监听事件后,data会被返回给用户。通常将event.data.fd设定为fd,这样就可以知道那个文件描述符触发事件。成功后,epoll_ctl()返回0.失败返回-1,并设置errno为下列值:EBADF epfd不是一个有效的epoll实例,或者fd不是有效文件描述符。EEXIST op为EPOLL_CTL_ADD,但是fd已经与epfd关联。EINVAL epfd不是一个epoll实例,epfd和fd相同,或者op无效。ENOENT op是EPOLL_CTL_MOD或者是EPOLL_CTL_DEL,但是fd没有与epfd关联。ENOMEN 没有足够内存完成进程的请求。EPERM fd不支持epoll。在epfd实例中加入一个fd指定的监听文件,使用如下代码:struct epoll_event event; int ret; event.data.fd = fd;/*return the fd to us later*/ event.events = EPOLLIN|EPOLLOUT ; ret = epoll_ctl (epfd,EPOLL_CTL_MOD,fd,&event); if (ret) perror ("epoll_ctl");修改epfd实例中的fd上的一个监听事件,可以使用如下代码:struct epoll_event event; int ret; event.data.fd = fd;/*return the fd to us later*/ event.events = EPOLLIN ; ret = epoll_ctl (epfd,EPOLL_CTL_MOD,fd,&event); if (ret) perror ("epoll_ctl");删除一个fd监听事件,可以使用如下代码:struct epoll_event event; int ret; event.data.fd = fd;/*return the fd to us later*/ event.events = EPOLLIN ; ret = epoll_ctl (epfd,EPOLL_CTL_DEL,fd,&event); if (ret) perror ("epoll_ctl");3、等待Epoll事件
epoll_wait()等待给定epoll实例关联的文件描述符上的事件:#include <sys/epoll.h> int epoll_wait (int epfd, struct epoll_event * * events, int maxevents, int timeout);对epoll_wait()的调用等待epoll实例epfd中的文件fd上的事件,时限为timeout毫秒。成功返回,events指向包含epoll_event结构体(该结构体描述了每个事件)的内存,且最多可以有maxevents个事件。返回值是事件数,出错返回-1,并将errno设置为以下值EBADF epfd是无效文件描述符EFAULT 进程对events指向的内存无写权限EINTR 系统调用在完成前被信号中断EINVAL epfd不是有效的epoll实例,或者maxevents小于等于0如果timeout 为0.即使没有事件发生,调用也立即发生,此时调用返回0.如果timeout为-1,调用将一直等待到有事件发生。当调用epoll_wait()返回,epoll_event结构体中的events数组描述了一次等待发生的事件,最多返回maxevents个事件。data字段包含了用户在调用epoll_ctl前的设置,如文件的句柄,用来区分那个文件所发生的事件。一个完整的epoll_wait()例子如下:#define MAX_EVENTS 64 struct epoll_event * events = NULL; int nr_events, i, epfd; events = malloc (sizeof(struct epoll_event) * MAX_EVENTS); if (! events ){ perror("malloc"); exit(-1); } nr_events = epoll_wait (epfd,events,MAX_EVENTS,-1); if (nr_events < 0){ perror("epoll_wait"); free(events); exit (-1); } for (int i=0; i<nr_eventsl i++) printf("event = %d on fd = %d \n", events[i].events,events[i].data.fd);4、边沿触发时间和水平触发事件
EPOLL事件有两种模型 Level Triggered (LT) 和 Edge Triggered (ET):
LT(level triggered,水平触发模式)是缺省的工作方式,并且同时支持 block 和 non-block socket。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。
ET(edge-triggered,边缘触发模式)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,等到下次有新的数据进来的时候才会再次出发就绪事件。
5、man epoll 中的实例
setnonblocking()函数将socket文件设置为非阻塞,因为使用的是ET模式。do_use_fd()是对此文件做出一定的处理,如读写等。
#define MAX_EVENTS 10 struct epoll_event ev, events[MAX_EVENTS]; int listen_sock, conn_sock, nfds, epollfd; /* Set up listening socket, 'listen_sock' (socket(), bind(), listen()) */ epollfd = epoll_create(10); if (epollfd == -1) { perror("epoll_create"); exit(EXIT_FAILURE); } ev.events = EPOLLIN; ev.data.fd = listen_sock; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) { perror("epoll_ctl: listen_sock"); exit(EXIT_FAILURE); } for (;;) { nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1); if (nfds == -1) { perror("epoll_pwait"); exit(EXIT_FAILURE); } for (n = 0; n < nfds; ++n) { if (events[n].data.fd == listen_sock) { conn_sock = accept(listen_sock, (struct sockaddr *) &local, &addrlen); if (conn_sock == -1) { perror("accept"); exit(EXIT_FAILURE); } setnonblocking(conn_sock); ev.events = EPOLLIN | EPOLLET; ev.data.fd = conn_sock; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock, &ev) == -1) { perror("epoll_ctl: conn_sock"); exit(EXIT_FAILURE); } } else { do_use_fd(events[n].data.fd); } } }
以一个生活中的例子来解释.假设你在大学中读书,要等待一个朋友来访,而这个朋友只知道你在A号楼,但是不知道你具体住在哪里,于是你们约好了在A号楼门口见面.如果你使用的阻塞IO模型来处理这个问题,那么你就只能一直守候在A号楼门口等待朋友的到来,在这段时间里你不能做别的事情,不难知道,这种方式的效率是低下的.进一步解释select和epoll模型的差异.select版大妈做的是如下的事情:比如同学甲的朋友来了,select版大妈比较笨,她带着朋友挨个房间进行查询谁是同学甲,你等的朋友来了,于是在实际的代码中,select版大妈做的是以下的事情:int n = select(&readset,NULL,NULL,100); for (int i = 0; n > 0; ++i) { if (FD_ISSET(fdarray[i], &readset)) { do_something(fdarray[i]); --n; } }epoll版大妈就比较先进了,她记下了同学甲的信息,比如说他的房间号,那么等同学甲的朋友到来时,只需要告诉该朋友同学甲在哪个房间即可,不用自己亲自带着人满大楼的找人了.于是epoll版大妈做的事情可以用如下的代码表示:n = epoll_wait(epfd,events,20,500); for(i=0;i<n;++i) { do_something(events[n]); } 在epoll中,关键的数据结构epoll_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 */ }; 可以看到,epoll_data是一个union结构体,它就是epoll版大妈用于保存同学信息的结构体,它可以保存很多类型的信息:fd,指针,等等.有了这个结构体,epoll大妈可以不用吹灰之力就可以定位到同学甲.别小看了这些效率的提高,在一个大规模并发的服务器中,轮询IO是最耗时间的操作之一.再回到那个例子中,如果每到来一个朋友楼管大妈都要全楼的查询同学,那么处理的效率必然就低下了,过不久楼底就有不少的人了.对比最早给出的阻塞IO的处理模型, 可以看到采用了多路复用IO之后, 程序可以自由的进行自己除了IO操作之外的工作, 只有到IO状态发生变化的时候由多路复用IO进行通知, 然后再采取相应的操作, 而不用一直阻塞等待IO状态发生变化了.从上面的分析也可以看出,epoll比select的提高实际上是一个用空间换时间思想的具体应用.二、深入理解epoll的实现原理:开发高性能网络程序时,windows开发者们言必称iocp,linux开发者们则言必称epoll。大家都明白epoll是一种IO多路复用技术,可以非常高效的处理数以百万计的socket句柄,比起以前的select和poll效率高大发了。我们用起epoll来都感觉挺爽,确实快,那么,它到底为什么可以高速处理这么多并发连接呢? 先简单回顾下如何使用C库封装的3个epoll系统调用吧。int epoll_create(int size); int epoll_ctl(int epfd, i......余下全文>>
明显是穿指针进去,会改变指针结构的值,没有copy操作。
评论暂时关闭