Epoll模型

概念:

  epoll是Linux内核为处理大批句柄而作改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著的减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。因为它会复用文件描述符集合来传递结果而不是迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一个原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了epoll除了提供select\poll那种IO事件的电平触发(Level Triggered)外,还提供了边沿触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提供应用程序的效率。

工作方式:

LT(level triggered):水平触发,缺省方式,同时支持block和no-block socket,在这种做法中,内核告诉我们一个文件描述符是否被就绪了,如果就绪了,你就可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错的可能性较小。传统的select\poll都是这种模型的代表。


ET(edge-triggered):边沿触发,高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪状态时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如:你在发送、接受或者接受请求,或者发送接受的数据少于一定量时导致了一个EWOULDBLOCK错误)。但是请注意,如果一直不对这个fd做IO操作(从而导致它再次变成未就绪状态),内核不会发送更多的通知。

高并发多路I/O复用之epoll模型

区别:

LT事件不会丢弃,而是只要buffer里面有数据可以让用户读取,则不断的通知你。而ET则只在事件发生之时通知。

使用方式:

使用方式:

  (先进行创建,在进行注册)

1int epoll_create(int size)

创建一个epoll句柄,参数size用来告诉内核监听的数目。

2int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

Epoll_ctl事件注册函数,

  参数epfdepoll的句柄;

参数op表示动作,用3个宏来表示:

EPOLL_CTL_ADD(注册新的fdepfd)

EPOLL_CTL_MOD(修改已经注册的fd的监听事件)

EPOLL_CTL_DEL(epfd删除一个fd)

  参数fd为需要监听的标符;

  参数event告诉内核需要监听的事件,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的事件的属性,有如下属性:*/  

  epoll_data_t data;  /* User data variable ,用户数据变量*/  

};

  其中events可以用以下几个宏的集合:

EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭)

EPOLLOUT:表示对应的文件描述符可以写

EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)

EPOLLERR:表示对应的文件描述符发生错误

EPOLLHUP:表示对应的文件描述符被挂断;

EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的

EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

3 int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)

  等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。

 

例如:简单实用过程,见附几个文件。

  程序共包含2个头文件和3cpp文件。其中3cpp文件中,每一个cpp文件都是一个应用程序,server.cpp:服务器程序,client.cpp:单个客户端程序,tester.cpp:模拟高并发,开启10000个客户端去连服务器。

utils.h头文件,就包含一个设置socket为不阻塞函数,如下:

 

//该函数自己定义,实现将套接字设置为非阻塞状态

int setnonblocking(int sockfd)

{

    CHK(fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK));

    return 0;

}

注:fcntl函数的使用方法;

所在头文件 #include <fcntl.h>,#include <unistd.h>

int fcntl(int fd, int cmd);

 

int fcntl(int fd, int cmd, long arg);         

 

int fcntl(int fd, int cmd, struct flock *lock);

fcntl()针对(文件)描述符提供控制.参数fd是被参数cmd操作(如下面的描述)的描述符,针对cmd的值,fcntl能够接受第三个参数(arg)

fcntl函数有5种功能:

     1.复制一个现有的描述符(cmd=F_DUPFD).

 

    2.获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).

 

    3.获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).

 

     4.获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).

 

     5.获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW).

相关文章: