epoll详解

http://blog.csdn.net/majianfei1023/article/details/45772269

欢迎转载,转载请注明原文地址:http://blog.csdn.net/majianfei1023/article/details/45772269


一.基本概念:

1.epoll是什么:
epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入就绪队列(Ready)的描述符集合就行了。epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait的调用,提高应用程序效率。

2.epoll,select,poll的区别:
select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
select和poll都需要在返回后,通过遍历文件描述符来获取已经就绪的socket。事实上,同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。


select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点,事实上从现在看来,这也是它所剩不多的优点之一。
select的几大缺点:
(1)单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,不过可以通过修改宏定义甚至重新编译内核的方式提升这一限制。但是随着文件描述符数量的增大,其复制的开销也线性增长。同时,由于网络响应时间的延迟使得大量TCP连接处于非活跃状态,但调用select()会对所有socket进行一次线性扫描,所以这也浪费了一定的开销。
(2)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大

(3)同时每次调用select都需要在内核进行线性遍历,时间复杂度为O(N),这个开销在fd很多时也很大

poll的缺点:
它和select在本质上没有多大差别,但是poll没有最大文件描述符数量的限制(也就是没有select的缺点1,却有它的缺点2和3)。
poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。


epoll的优点主要是以下几个方面:
1. 监视的描述符数量不受限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左 右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。 (我2G内存的机器上是239545)。select的最大缺点就是进程打开的fd是有数量限制的

2. IO的效率不会随着监视fd的数量的增长而下降。epoll不同于select和poll轮询的方式,而是通过每个fd定义的回调函数来实现的。只有就绪的fd才会执行回调函数,时间复杂度为O(1)


3. 支持电平触发和边沿触发两种方式(具体区别,看下面的2.2epoll详解-工作模式),理论上边缘触发的性能要更高一些,但是代码实现相当复杂。

4. mmap加速内核与用户空间的信息传递。epoll是通过内核于用户空间mmap同一块内存,避免了无谓的内存拷贝。

通过表格来看一下更清楚。

select

poll

epoll

支持最大连接数

1024(2048)

无上限

无上限

IO效率

每次调用进行线性遍历,时间复杂度为O(N)

每次调用进行线性遍历,时间复杂度为O(N)

使用“事件”通知方式,每当fd就绪,系统注册的回调函数就会被调用,将就绪fd放到rdllist里面,这样epoll_wait返回的时候我们就拿到了就绪的fd。时间发复杂度O(1)

fd拷贝

每次select都拷贝

每次poll都拷贝

调用epoll_ctl时拷贝进内核并由内核保存,之后每次epoll_wait不拷贝





二.epoll详解:
既然了解了epoll的基本概念和优点,那我们就来看看epoll怎么用。
1.epoll的接口:
epoll操作过程需要三个接口,分别如下:
[cpp] view plain copy
  1. #include <sys/epoll.h>  
  2. int epoll_create(int size);  
  3. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);  
  4. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);  


(1) int epoll_create(int size);
  创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。在linux-2.4.32内核中根据size大小初始化哈希表的大小,在linux2.6.10内核中该参数无用,使用红黑树管理所有的文件描述符,而不是hash。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。


(2)int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注册函数,它不同于select()在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
第一个参数epfd是epoll_create()的返回值;

第二个参数op表示动作,用三个宏来表示:

  EPOLL_CTL_ADD:注册新的fd到epfd中,
  EPOLL_CTL_MOD:修改已经注册的fd的监听事件,
  EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数fd是需要监听的fd;
第四个参数*event是告诉内核需要监听什么事,struct epoll_event结构如下:

[cpp] view plain copy
  1. struct epoll_event {  
  2.   __uint32_t events;  /* Epoll events */  
  3.   epoll_data_t data;  /* User data variable */  
  4. };  
  5.   
  6.   
  7. typedef union epoll_data {    
  8. void *ptr;    
  9. int fd;    
  10. __uint32_t u32;    
  11. __uint64_t u64;    
  12. } epoll_data_t;  

events可以是以下几个宏的集合:
  EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
  EPOLLOUT:表示对应的文件描述符可以写;
  EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
  EPOLLERR:表示对应的文件描述符发生错误;
  EPOLLHUP:表示对应的文件描述符被挂断;
  EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
  EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket(也就是第三个参数fd还是epoll_data.fd)的话,需要再次把这个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表示已超时。


epoll_wait运行的原理是
等侍注册在epfd上的socket fd的事件的发生,如果发生则将发生的sokct fd和事件类型放入到events数组中。
并 且将注册在epfd上的socket fd的事件类型给清空,所以如果下一个循环你还要关注这个socket fd的话,则需要用epoll_ctl(epfd,EPOLL_CTL_MOD,listenfd,&ev)来重新设置socket fd的事件类型。这时不用EPOLL_CTL_ADD,因为socket fd并未清空,只是事件类型清空。这一步非常重要。


2.epoll工作模式:
EPOLL事件有两种模型:
LT(level triggered-电平触发)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作或者未处理完,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表。
ET(edge triggered-边缘触发)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误),边缘触发的意思就是在两个状态的临界值进行改变的时候触发。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。
  ET和LT的区别就在这里体现,LT事件不会丢弃,而是只要读buffer里面有数据可以让用户读,则不断的通知你。而ET则只在事件发生之时通知。可以简单理解为LT是水平触发,而ET则为边缘触发。LT模式只要有事件未处理就会触发,而ET则只在高低电平变换时(即状态从1到0或者0到1)触发。
  ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。


三.epoll的例子:

[cpp] view plain copy
  1. #include <stdio.h>  
  2. #include <unistd.h>  
  3. #include <stdlib.h>  
  4. #include <string.h>  
  5. #include <sys/types.h>  
  6. #include <errno.h>  
  7. #include <sys/socket.h>  
  8. #include <netinet/in.h>  
  9. #include <sys/epoll.h>  
  10. #include <fcntl.h>  
  11.   
  12. #define MAX_EPOLL       1000  
  13. #define BUF_SIZE        1024  
  14. #define PORT            6000  
  15.   
  16.   
  17. int setnonblocking( int fd )  
  18. {  
  19.     if( fcntl( fd, F_SETFL, fcntl( fd, F_GETFD, 0 )|O_NONBLOCK ) == -1 )  
  20.     {  
  21.         printf("Set blocking error : %d\n", errno);  
  22.         return -1;  
  23.     }  
  24.     return 0;  
  25. }  
  26.   
  27. int main( int argc, char ** argv )  
  28. {  
  29.     int         listen_fd;  
  30.     int         conn_fd;  
  31.     int         epoll_fd;  
  32.     int         nread;  
  33.     int     i;  
  34.     struct sockaddr_in servaddr;  
  35.     struct sockaddr_in cliaddr;  
  36.     struct  epoll_event ev;  //监听fd  
  37.     struct  epoll_event events[MAX_EPOLL];  
  38.     char    buf[BUF_SIZE];  
  39.     socklen_t   len = sizeofstruct sockaddr_in );  
  40.       
  41.     bzero( &servaddr, sizeof( servaddr ) );  
  42.     servaddr.sin_family = AF_INET;  
  43.     servaddr.sin_addr.s_addr = htonl( INADDR_ANY );  
  44.     servaddr.sin_port = htons( PORT );  
  45.       
  46.     if( ( listen_fd = socket( AF_INET, SOCK_STREAM, 0 ) ) == -1 )  
  47.     {  
  48.         printf("socket error...\n" , errno );  
  49.         exit( EXIT_FAILURE );  
  50.     }  
  51.       
  52.     if( setnonblocking( listen_fd ) == -1 )  
  53.     {  
  54.         printf("setnonblocking error : %d\n", errno);  
  55.         exit( EXIT_FAILURE );  
  56.     }  
  57.       
  58.     if( bind( listen_fd, ( struct sockaddr *)&servaddr, sizeofstruct sockaddr ) ) == -1 )  
  59.     {  
  60.         printf("bind error : %d\n", errno);  
  61.         exit( EXIT_FAILURE );  
  62.     }  
  63.   
  64.     if( listen( listen_fd, 10 ) == -1 )  
  65.     {  
  66.         printf("Listen Error : %d\n", errno);  
  67.         exit( EXIT_FAILURE );  
  68.     }  
  69.       
  70.     epoll_fd = epoll_create( MAX_EPOLL );   //1.epoll_create  
  71.     ev.events = EPOLLIN | EPOLLET;  
  72.     ev.data.fd = listen_fd;  
  73.     if( epoll_ctl( epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev ) < 0 )       //2.epoll_ctl  
  74.     {  
  75.         printf("Epoll Error : %d\n", errno);  
  76.         exit( EXIT_FAILURE );  
  77.     }  
  78.       
  79.     while( 1 )  
  80.     {  
  81.         int ready_counts = 0;  
  82.         if( ( ready_counts = epoll_wait( epoll_fd, events, MAX_EPOLL, -1 ) ) == -1 )        //3.epoll_wait,就绪的event在events里面  
  83.         {  
  84.             printf( "Epoll Wait Error : %d\n", errno );  
  85.             exit( EXIT_FAILURE );  
  86.         }  
  87.   
  88.         for( i = 0; i < ready_counts; i++ )  
  89.         {  
  90.             if( events[i].data.fd == listen_fd)     //监听端口有就绪事件  
  91.             {  
  92.                 if( ( conn_fd = accept( listen_fd, (struct sockaddr *)&cliaddr, &len ) ) == -1 )  
  93.                 {  
  94.                     printf("Accept Error : %d\n", errno);  
  95.                     exit( EXIT_FAILURE );  
  96.                 }  
  97.                   
  98.                 printf( "Server get from client !\n"/*,  inet_ntoa(cliaddr.sin_addr), cliaddr.sin_port */);  
  99.                   
  100.                 ev.events = EPOLLIN | EPOLLET;  
  101.                 ev.data.fd = conn_fd;  
  102.                 if( epoll_ctl( epoll_fd, EPOLL_CTL_ADD, conn_fd, &ev ) < 0 )  
  103.                 {  
  104.                     printf("Epoll Error : %d\n", errno);  
  105.                     exit( EXIT_FAILURE );  
  106.                 }  
  107.             }  
  108.             else  
  109.             {  
  110.                 nread = read( events[i].data.fd, buf, sizeof( buf ) );  
  111.                 if( nread <= 0 )  
  112.                 {  
  113.                     close( events[i].data.fd );  
  114.                     epoll_ctl( epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, &ev );  
  115.                     continue;  
  116.                 }  
  117.                   
  118.                 write( events[i].data.fd, buf, nread );  
  119.             }  
  120.         }  
  121.     }  
  122.       
  123.     close( listen_fd );  
  124.     return 0;  
  125. }  

四:总结:

既然epoll有这么多优点,是不是可以取代select和poll。

什么情况下用select/poll而不用epoll呢?


1.epoll更适合于处理大量的fd ,且活跃fd不是很多的情况。
反之如果处理的fd量不大,且基本都是活跃的,epoll并不比select/poll有什么效率。相反,过多使用epoll_ctl,效率相比还有稍微的下降。这时候使用select既简单又方便,就没必要用epoll这么复杂的写法了。
2.select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/383766.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据分割-并查集+set

小w来到百度之星的赛场上&#xff0c;准备开始实现一个程序自动分析系统。 这个程序接受一些形如xixj 或 xi≠xj 的相等/不等约束条件作为输入&#xff0c;判定是否可以通过给每个 w 赋适当的值&#xff0c;来满足这些条件。 输入包含多组数据。 然而粗心的小w不幸地把每组数据…

linux c++线程池的实现

http://blog.csdn.net/zhoubl668/article/details/8927090?t1473221020107 线程池的原理大家都知道&#xff0c;直接上代码了^_^ Thread.h [cpp] view plaincopy #ifndef __THREAD_H #define __THREAD_H #include <vector> #include <string> #inc…

树启发式合并入门

所谓启发式合并&#xff0c;就是一种符合直觉的合并方法&#xff1a;将小的子树合并在大的子树上。 这些问题一般是相似的问题背景&#xff1a;都是树上的计数问题&#xff0c;都不能直接从上往下进行暴力&#xff0c;都需要从下往上计数时对子树信息进行运算从而得到父亲节点的…

链栈基本操作

http://blog.csdn.net/jwentao01/article/details/46765517###;栈基本概念&#xff1a; 栈&#xff08;stack&#xff09;是限定在表尾进行插入和删除操作的线性表&#xff08;或单链表&#xff09;。 //只能在一段进行插入和删除&#xff0c;因此不存在&#xff0c;在中间进行…

Linux网络编程---I/O复用模型之select

https://blog.csdn.net/men_wen/article/details/53456435Linux网络编程—I/O复用模型之select 1. IO复用模型 IO复用能够预先告知内核&#xff0c;一旦发现进程指定的一个或者多个IO条件就绪&#xff0c;它就通知进程。IO复用阻塞在select或poll系统调用上&#xff0c;而不是阻…

UVa12633-Super Rooks on Chessboard-容斥+FFT

题目大意就是给你一个R*C的棋盘&#xff0c;上面有超级兵&#xff0c;这种超级兵会攻击 同一行、同一列、同一主对角线的所有元素&#xff0c;现在给你N个超级兵的坐标&#xff0c;需要你求出有多少方块是不能被攻击到的(R,C,N<50000) 遇到这种计数问题就要联想到容斥&#…

Linux网络编程---I/O复用模型之poll

https://blog.csdn.net/men_wen/article/details/53456474Linux网络编程—I/O复用模型之poll 1.函数poll poll系统调用和select类似&#xff0c;也是在指定时间内轮询一定数量的文件描述符&#xff0c;以测试其中是否有就绪者。 #include <poll.h>int poll(struct pollfd…

FFT模板

整理了一下&#xff0c;自己写了一下模板 const double PIacos(-1.0); struct complex {double r,i;complex(double _r0,double _i0):r(_r),i(_i){}complex operator (const complex &b) {return complex(rb.r,ib.i);}complex operator -(const complex &b) {return c…

Linux网络编程---I/O复用模型之epoll

https://blog.csdn.net/men_wen/article/details/53456474 Linux网络编程—I/O复用模型之epoll 1. epoll模型简介 epoll是Linux多路服用IO接口select/poll的加强版&#xff0c;e对应的英文单词就是enhancement&#xff0c;中文翻译为增强&#xff0c;加强&#xff0c;提高&…

POJ 1741tree-点分治入门

学习了一下点分治&#xff0c;如果理解有误还请不吝赐教。 为了快速求得树上任意两点之间距离满足某种关系的点对数&#xff0c;我们需要用到这种算法。 点分治是树上的一种分治算法&#xff0c;依靠树和子树之间的关系进行分治从而降低复杂度。 和其他树上的算法有一些区别…

基于单链表的生产者消费者问题

『生产者与消费者问题分析』「原理」生产者生产产品&#xff0c;消费者消费产品。产品如果被消费者消费完了&#xff0c;同时生产者又没有生产出产品&#xff0c;消费者 就必须等待。同样的&#xff0c;如果生产者生产了产品&#xff0c;而消费者没有去消费&#x…

C++智能指针(一)智能指针的简单介绍

https://blog.csdn.net/nou_camp/article/details/70176949C智能指针 在正式了解智能指针前先看一下下面的一段代码 #include<iostream> using namespace std; class A { public:A():_ptr(NULL), _a(0){}~A(){} public:int* _ptr;int _a; };void test() {A a;int *p1 ne…

聪聪可可-点分治

聪聪和可可是兄弟俩&#xff0c;他们俩经常为了一些琐事打起来&#xff0c;例如家中只剩下最后一根冰棍而两人都想吃、两个人都想玩儿电脑&#xff08;可是他们家只有一台电脑&#xff09;……遇到这种问题&#xff0c;一般情况下石头剪刀布就好了&#xff0c;可是他们已经玩儿…

C++智能指针(二)模拟实现三种智能指针

https://blog.csdn.net/nou_camp/article/details/70186721在上一篇博客中提到了Auto_ptr(C智能指针&#xff08;一&#xff09;)&#xff0c;下面进行模拟实现Auto_ptr 采用类模板实现 #include<iostream> using namespace std; template<class T> class Autoptr …

Prime Distance On Tree-树分治+FFT

题目描述 Problem description. You are given a tree. If we select 2 distinct nodes uniformly at random, what’s the probability that the distance between these 2 nodes is a prime number? Input The first line contains a number N: the number of nodes in this…

C++智能指针(三)总结

https://blog.csdn.net/nou_camp/article/details/70195795 在上一篇博客中&#xff08;C智能指针&#xff08;二&#xff09;&#xff09;模拟实现了三种智能指针。 其中最好的就是shared_ptr,但是这并不代表它就是最完美的&#xff0c;它也有问题&#xff0c;这个问题就是循环…

POJ2114-Boatherds-树分治

题目描述 Boatherds Inc. is a sailing company operating in the country of Trabantustan and offering boat trips on Trabantian rivers. All the rivers originate somewhere in the mountains and on their way down to the lowlands they gradually join and finally th…

c++11 你需要知道这些就够了

https://blog.csdn.net/tangliguantou/article/details/50549751c11新特性举着火把寻找电灯今天我就权当抛砖引玉&#xff0c;如有不解大家一起探讨。有部分内容是引用自互联网上的内容&#xff0c;如有问题请联系我。T&& 右值引用 std::move 右值引用出现之前我们只能…

HDU5977-Garden of Eden-树分治+FWT

题目描述 When God made the first man, he put him on a beautiful garden, the Garden of Eden. Here Adam lived with all animals. God gave Adam eternal life. But Adam was lonely in the garden, so God made Eve. When Adam was asleep one night, God took a rib fro…

C++11新特性学习

https://blog.csdn.net/tennysonsky/article/details/778170481、什么是C11C11标准为C编程语言的第三个官方标准&#xff0c;正式名叫ISO/IEC 14882:2011 - Information technology -- Programming languages -- C。在正式标准发布前&#xff0c;原名C0x。它将取代C标准第二版I…