epoll的使用

在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:存储所有的读写事件。常用的事件类型有:

  1. EPOLLIN :表示对应的文件描述符可以读,如果另一方断开SOCKET连接,也会是返回可以读的事件;
  2. EPOLLOUT:表示对应的文件描述符可以写;
  3. EPOLLPRI:表示对应的文件描述符有紧急的数据可读;
  4. EPOLLERR:表示对应的文件描述符产生错误;
  5. EPOLLHUP:表示对应的文件描述符被挂断;
  6. EPOLLET:将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的,下文会简单说明这两种工作模式的不同;
  7. 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的事务类型给清空,所以若是以后还要用这个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完)就会一直通知下去的。


五,使用例子

  1. 通过create_epoll(int maxfds)来创建一个epoll的句柄,该函数调用之后会返回一个新的epoll句柄,之后的所有的操作都是通过这个句柄来进行操作,由于这个句柄占用一个文件描述符,所以在用完之后,需要用close()来关闭这个创建出来的epoll句柄。
  2. 在应用的主循环中,调用epoll_wait,每一次调用都会查询所有的网络socket是否有状态变化。
    如:nfds = epoll_wait(epfd,events,150,200);这里的epfd是通过create_epoll调用返回的句柄,events是epoll_event*的一个指针,当这个函数返回时,这个指针就存储了所有可以读写的socket。150是events变量有多大(注意:这个值不能比epoll_create调用时传递的maxfds参数大),而200是一个超时时间,以毫秒为单位。
  3. 处理。一般的结构如下:
     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
                {
                }
            }
     }

你可能感兴趣的:(epoll)