在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专用的文件描述符,会向内核申请一空间,用来存放你想存眷的socket fd上是否产生以及产生了什么事务。
二,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:存储所有的读写事件。常用的事件类型有:
- EPOLLIN :表示对应的文件描述符可以读,如果另一方断开SOCKET连接,也会是返回可以读的事件;
- EPOLLOUT:表示对应的文件描述符可以写;
- EPOLLPRI:表示对应的文件描述符有紧急的数据可读;
- EPOLLERR:表示对应的文件描述符产生错误;
- EPOLLHUP:表示对应的文件描述符被挂断;
- EPOLLET:将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的,下文会简单说明这两种工作模式的不同;
- EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里;
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的事务类型给清空,所以若是以后还要用这个socket fd的话,则须要用epoll_ctl(epfd,EPOLL_CTL_MOD,listenfd,&ev)来从头设置socket fd的事务类型。但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完)就会一直通知下去的。
五,使用例子
- 通过create_epoll(int maxfds)来创建一个epoll的句柄,该函数调用之后会返回一个新的epoll句柄,之后的所有的操作都是通过这个句柄来进行操作,由于这个句柄占用一个文件描述符,所以在用完之后,需要用close()来关闭这个创建出来的epoll句柄。
- 在应用的主循环中,调用epoll_wait,每一次调用都会查询所有的网络socket是否有状态变化。
如:nfds = epoll_wait(epfd,events,150,200);这里的epfd是通过create_epoll调用返回的句柄,events是epoll_event*的一个指针,当这个函数返回时,这个指针就存储了所有可以读写的socket。150是events变量有多大(注意:这个值不能比epoll_create调用时传递的maxfds参数大),而200是一个超时时间,以毫秒为单位。 - 处理。一般的结构如下:
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 { } } }