linux epoll详解及使用方法概述

来源:互联网 发布:php长连接 消息推送 编辑:程序博客网 时间:2024/05/22 19:57

一、什么是epoll

于Linux 2.5.44首度登场的epoll是Linux内核的可扩展I/O事件通知机制。它设计目的只在取代既有POSIX select(2)与poll(2)系统函数,让需要大量操作文件描述符的程序得以发挥更优异的性能(举例来说:旧有的系统函数所花费的时间复杂度为O(n),epoll则耗时O(1))。---维基百科

简要概括其主要特性:

1、  和select poll一样,一种I/O多路复用技术;

2、  只关注活跃的连接,而无需遍历所有的描述符集合;

3、  可以处理大量的链接请求(最大可以到系统可以打开的文件数目 /proc/sys/fs/file-max


二、和select、poll的比较

相比于selectepoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。并且,在linux/posix_types.h头文件有这样的声明:
#define __FD_SETSIZE    1024

表示select最多同时监听1024个fd,当然,可以通过修改头文件再重编译内核来扩大这个数目,但这似乎并不治本。

poll基本上效率和select是相同的。

Epoll没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大于2048, 一般来说这个数目和系统内存关系很大,具体数目可以cat/proc/sys/fs/file-max察看。Epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,Epoll的效率就会远远高于selectpoll


三、epoll高效的原因

对于select模型当有I/O事件到来时,select通知应用程序有事件到了快去处理,而应用程序必须轮询所有的FD集合,测试每个FD是否有事件发生,并处理事件;代码像下面这样:

int res = select(maxfd+1, &readfds, NULL, NULL, 120);if(res > 0){    for(int i = 0; i < MAX_CONNECTION; i++)    {        if(FD_ISSET(allConnection[i],&readfds))        {            handleEvent(allConnection[i]);        }    }}// if(res == 0) handle timeout, res < 0 handle error


Epoll不仅会告诉应用程序有I/0事件到来,还会告诉应用程序相关的信息,这些信息是应用程序填充的,因此根据这些信息应用程序就能直接定位到事件,而不必遍历整个FD集合。

int res = epoll_wait(epfd, events, 20, 120);for(int i = 0; i < res;i++){    handleEvent(events[n]);}

比如当服务器处理200个连接时,它将为需要写入或读取数据的每个连接提供服务,然后需要等待有更多的工作要做。当他在等待的时候,200个连接中的任意一个连接接收到数据,服务将被中断。
使用select时,内核会将进程添加到200个等待list中,每个连接一个,要做到这一点,需要一个“thunk”来将进程附加到等待list中。当进程最终醒来的时候,需要从所有200个等待list中移除,所有的thunk需要被释放。
相比于epoll,epoll的socket自己有一个等待list,该过程只需要使用一个等待list,只使用一个”thunk”。当进程被唤醒时,它只需要从一个等待列表中删除,只有一个thunk需要被清除,使用epoll时,epoll套接字本身attach到200个连接中的每一个,但是这仅需要在最初的时候做一次,当各个连接被删除时,它也仅需销毁一次。相比而言,每个调用select的阻塞操作必须将进程添加到正在监视的每个套接字的每个等待队列中。
对于select,最大的开销在于检测没有活动的socket是否开始有活动了,对于epoll,不需要检测那些没有活动的socket,因为当活动发生时,它将会通知epoll的socket,每次调用select查看是否有活动时,select会轮询各个socket,而epoll是让活动的套接字自己通知进程。


四、epoll的使用

1、创建epoll

/*** @param size 告诉内核监听的数目** @returns 返回一个epoll句柄(即一个文件描述符)*/int epoll_create(int size);
创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。其实是申请一个内核空间,用来存放你想关注的socketfd上是否发生以及发生了什么事件需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。


2、控制epoll

/*** @param epfd 用epoll_create所创建的epoll句柄* @param op 表示对epoll监控描述符控制的动作** EPOLL_CTL_ADD(注册新的fd到epfd)* EPOLL_CTL_MOD(修改已经注册的fd的监听事件)* EPOLL_CTL_DEL(epfd删除一个fd)** @param fd 需要监听的文件描述符* @param event 告诉内核需要监听的事件** @returns 成功返回0,失败返回-1, errno查看错误信息*/int epoll_ctl(int epfd, int op, int fd,struct epoll_event *event);struct epoll_event {__uint32_t events; /* epoll 事件 */epoll_data_t data; /* 用户传递的数据 */}/** events : {EPOLLIN, EPOLLOUT, EPOLLPRI,EPOLLHUP, EPOLLET, EPOLLONESHOT}*/typedef union epoll_data {void *ptr;int fd;uint32_t u32;uint64_t u64;} epoll_data_t;struct epoll_event new_event;new_event.events = EPOLLIN | EPOLLOUT;new_event.data.fd = 5;epoll_ctl(epfd, EPOLL_CTL_ADD, 5, &new_event);
epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。

3、等待epoll

/**** @param epfd 用epoll_create所创建的epoll句柄* @param event 从内核得到的事件集合* @param maxevents 告知内核这个events有多大,* 注意: 值 不能大于创建epoll_create()时的size.* @param timeout 超时时间* -1: 永久阻塞* 0: 立即返回,非阻塞* >0: 指定微秒** @returns 成功: 有多少文件描述符就绪,时间到时返回0* 失败: -1, errno 查看错误*/int epoll_wait(int epfd, struct epoll_event *event,int maxevents, int timeout);struct epoll_event my_event[1000];int event_cnt = epoll_wait(epfd, my_event, 1000, -1);
等待IO事件的发生,类似于select()调用。


4、epoll编程框架

//创建 epollint epfd = epoll_crete(1000);//将 listen_fd 添加进 epoll 中epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &listen_event);while (1) {//阻塞等待 epoll 中 的fd 触发  int active_cnt = epoll_wait(epfd, events, 1000, -1);  for (i = 0; i < active_cnt; i++) {    if (evnets[i].data.fd == listen_fd) {//accept. 并且将新accept 的fd 加进epoll中.    }  else if (events[i].events & EPOLLIN) {//对此fd 进行读操作    }  else if (events[i].events & EPOLLOUT) {//对此fd 进行写操作    }  }}  




参考:

http://blog.csdn.net/sparkliang/article/details/4770655#

http://stackoverflow.com/questions/17355593/why-is-epoll-faster-than-select/17355702#17355702

http://blog.csdn.net/ljx0305/article/details/4065058



0 0