在linux上,执行man epoll之后,可以得到这些结果
NAME epoll - I/O event notification facility SYNOPSIS #include <sys/epoll.h> DESCRIPTION epoll is a variant of poll(2) that can be used either as Edge or Level Trig- gered interface and scales well to large numbers of watched fds. Three system calls are provided to set up and control an epoll set: epoll_create(2), epoll_ctl(2), epoll_wait(2). An epoll set is connected to a file descriptor created by epoll_create(2). Interest for certain file descriptors is then registered via epoll_ctl(2). Finally, the actual wait is started by epoll_wait(2).epoll相关的的系统调用有:epoll_create, epoll_ctl和epoll_wait。
epoll_create用来创建一个epoll文件描述符。
epoll_ctl用来添加、修改、删除需要侦听的文件描述符及其事件。
epoll_wait/epoll_pwait 接收发生在被侦听的描述符上的,用户感兴趣的IO事件。
这个条件符使用完之后,直接用close关闭即可。另外,任何被侦听的文件描述拊只要其被关闭,那么它也会自动的从被侦听的文件描述符集体中删除。
一,epoll_create
函数声明如下: int epoll_create(int maxfds)
参数表示EPOLL所支持的最大句柄数,函数会返回一个新的EPOLL句柄,之后的所有操作将通过这个句柄来进行操作。该函数生成一个epoll专用的文件描述符,会向内核申请一空间,用来存放你想存眷的socketfd上是否产生以及产生了什么事务。
二,epoll_wait
函数声明如下:int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout)。
这个函数在网络的主循环里面调用,每一次调用都会查询所有的网络接口,查看哪一个可以读取,哪一个可以写数据了。一般这个函数返回之后,后面都是一个循环体,用于处理所有返回的可以读写的网络设备,如
for(i=0;i<nfds;i++){ if(event[n].data.fd == listenXXfd){ accept(...); } }返回的数据,全部存储在events结构体中。
epfd : 是epoll_create 创建返回的句柄。
events:存储所有的读写事件。常用的事件类型有:
maxevents : 每次最多处理的事件数。
timeout:表示epoll_wait的超时,如果设为0,表示马上返回,-1的时候,会一直等下去,直到有事件发生。
在使用的时候,如:
struct epoll_event ev; ev.data.fd= 网络文件描述符; ev.events=EPOLLIN|EPOLLET;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_wait在调用时,要注意其原理是:等侍注册在epfd上的socket fd的事务的产生,若是产生则将产生的sokct fd和事务类型放入到events数组中。并且将注册在epfd上的socket fd的事务类型给清空,所以若是以后还要用这个socketfd的话,则须要用epoll_ctl(epfd,EPOLL_CTL_MOD,listenfd,&ev)来从头设置socketfd的事务类型。但socket fd并未清空,这一步很是首要。
三,epoll_ctl
这个函数添加,修改或删除被侦听文件描述符,一般这个操作不要频繁的调用,太多的调用,可能会成为系统性能的杀手。
函数声明如下:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
epfd:由 epoll_create 生成的epoll专用的文件描述符;
op:要进行的操纵例如注册事务,可能的取值EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD 修 改、EPOLL_CTL_DEL 删除
fd:接入关系的socket文件描述符;
event:指向epoll_event的指针;
若是调用成功返回0,不成功返回-1
四,两种工作模式
epoll的操作是很简单的,一共就几个API。调用也很简单。下面简单了解一下EPOOL的两种工作模式ET和LT,就是边缘触发和水平触发,这是什么意思呢?
如果边缘触发模式时,在调用epoll_wait时,仅当状态发生变化的时候才会返回,就是会通知应用层,这有一点和水平触发区别的是:如果数据是在缓冲区中,边缘触发不认为这是一种状态变化。而水平触发模式时,当缓冲区有数据没有读取完(这个数据可能是上次状态变化时从网络上读取的),也会给应用层一个通知。也就是说,如果要采用边缘模式,那么我们需要一直read/write,直到出错没有数据,要不然数据就可能会丢失。而水平触发模式是只要有数据没有处理(就是还没有read完)就会一直通知下去的。
五,使用例子
for(;;){ int nfds = epoll_wait(epfd,events,20,500); for(i=0;i<nfds;++i) { if(events[i].data.fd==listenfd) //listenfd表示在服务器上一个用于监听的socket,此时表示有新的连接进来。 { connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen); //accept这个连接 struct epoll_event event; event.data.fd=connfd; event.events=EPOLLIN|EPOLLET; epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&event); //将新的fd添加到epoll的监听队列中 } else if( events[i].events&EPOLLIN ) //表示有数据可以被读 { n = read(sockfd,buffer,MAXLINE)) ; //读 event.data.ptr = md; //md为自定义类型,添加数据 event.events=EPOLLOUT|EPOLLET; epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&event);//这里一定要重新修改socket监听的事件。 } else if(events[i].events&EPOLLOUT) { send(sockfd,buffer, strlen(buffer), 0 ); event.data.fd=sockfd; event.events=EPOLLIN|EPOLLET; epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&event); } else { } } }