一:概述  

1:简介

EPOLL类似于POLL,是Linux特有的一种IO多路复用的机制。它在2.5.44内核中引入。

对于大量的描述符处理,EPOLL更有优势,它提供了三个系统调用来创建管理epoll实例:

epoll_create创建一个epoll实例,返回该实例的文件描述符;

epoll_ctl注册感兴趣的特定文件描述符,注册的描述符集合称为epoll集合;

epoll_wait监听IO事件;

2:水平触发和边沿触发

EPOLL事件分发接口有两种工作方式:边沿触发(edge-triggered, ET)和水平触发(level-triggered,LT)。边沿触发模式是epoll的高效工作模式。

水平触发是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表。

两种方式的不同可以通过下面的示例说明:

1:rfd是管道读一端的描述符,并且在epoll实例上进行了注册

2:管道另一端写入了2KB的数据

3:调用epoll_wait返回,rfd成为可读描述符

4:从rfd中读取1KB的数据

5:调用epoll_wait

如果rfd在注册到epoll实例时使用了EPOLLET标志,也就是边沿触发,则尽管在缓冲区中依然有数据可读,第5步的epoll_wait调用还是会阻塞,同时管道的另一端有可能在等待发送数据的回应。

原因在于,边沿触发模式,只有在监控的文件描述符状态发生变化的时候,才会触发事件。所以,第5步的epoll_wait有可能一直阻塞下去。上面的示例,第2步的数据写入导致了rfd上事件的产生,并且在第3步中对事件进行了触发。因第4步的读操作没有完全消费掉缓冲区中的数据,所以,第5步的epoll_wait会永远阻塞下去。

对于监听可读事件时,如果是socket是监听socket,那么当有新的主动连接到来为状态发生变化;对一般的socket而言,协议栈中相应的缓冲区有新的数据为状态发生变化。但是,如果在一个时间同时接收了N个连接(N>1),但是监听socket只accept了一个连接,那么其它未
accept的连接将不会在ET模式下给监听socket发出通知,此时状态不发生变化;对于一般的socket,如果对应的缓冲区本身已经有了N字节的数据,而只取出了小于N字节的数据,那么残存的数据不会造成状态发生变化。

仅当对方的动作(发出数据,关闭连接等)造成的事件才能导致状态发生变化,而本方协议栈中已经处理的事件(包括接收了对方的数据,接收了对方的主动连接请求)并不是造成状态发生变化的必要条件,状态变化一定是对方造成的。

使用边沿触发模式的epoll接口,建议是:将文件描述符置为非阻塞;只有在read或write返回EAGAIN后才继续等待事件。

即使在边沿触发模式下,如果收到了多次数据,那么事件也会产生多次。调用者可以指定EPOLLONESHOT标志,使得epoll在接收到一个事件之后,便不再监听相应的文件描述符。如果使用了该标志,则需要调用者用EPOLL_CTL_MOD,调用epoll_ctl,将该文件描述符重新置为监听。

3:/proc 接口限制

下面的接口可用来限制内核中epoll使用的内存总量:

/proc/sys/fs/epoll/max_user_watches(since Linux 2.6.28)

该接口限定了,每个实际用户ID,可以在所有epoll实例上注册的描述符总数。每个注册的描述符,在32位系统上大约耗费90字节,在64位系统上大约耗费160字节。

4:注意

如果在同一个epoll实例上,注册同一个描述符两次的话,则会返回EEXIST错误。

然而,可以向同一个epoll实例添加重复的描述符(dup, dup2),当重复的描述符注册不同的事件时,使用这种技巧可以用来过滤事件。

如果向两个epoll实例注册了相同的描述符,那么事件触发时,会通知到所有的epoll实例,但是这么做时,一定要小心。

epoll实例的描述符本身,也可以使用poll/epoll/select进行监听,如果该epoll实例正在等待事件,则该epoll实例就是可读的。

如果试图将epoll实例描述符注册到自己的epoll实例上,那么epoll_ctl会返回EINVAL错误。

尽管可以通过UNIX域套接字来传递epoll文件描述符,但是这样做是没有意义的,因为接收进程没有该epoll实例的描述符集合的副本。

关闭一个描述符,会导致该描述符从所有epoll集合中自动被移除。但前提是该描述符没有通过dup等函数进行过复制。一个描述符,在通过dup、dup2、fcntl的F_DUPFD,或者fork之后,会产生一个新的重复描述符,指向相同的文件表。因此,只有当引用同一文件表的所有重复描述符都关闭之后(引用计数为0),该描述符才会从epoll集合中删除。(可以使用EPOLL_CTL_DEL调用epoll_ctl来明确删除epoll集合中的该描述符)

这意味着,即使epoll集合中的某个描述符被关闭了,但是只要该描述符有打开的重复描述符,那该描述符上的事件,依然会被报告。

如果在epoll_wait调用时,发生了多个事件,则这些事件会合在一起进行报告。

在使用边沿触发时,是否需要持续调用read/write,直到它们返回EAGAIN,这取决于描述符类型,如果描述符是packet/token-oriented类型的,比如UDP数据报,或者canonical模式下的终端,则探测IO读写空间耗尽唯一方法就是持续调用read/write直到返回EAGAIN。

对于流式文件,比如管道、FIFO、流套接字等,则探测IO读写空间耗尽,还可以通过检测read/write返回值进行判断。比如,若调用read请求一定数量的数据,但是read返回值小于该请求数,则可以断定该描述符的IO读空间已经耗尽了。这种情形同样类似于write。

二:epoll系统调用

1:epoll_create、epoll_create1

  1. #include<sys/epoll.h>
  2.  
  3. int epoll_create(int size);
  4. int epoll_create1(int flags);

epoll_create创建一个epoll实例。size参数,自Linux2.6.8开始就被忽略了,但是它的值必须大于0。

epoll_create返回epoll实例的文件描述符,该描述符用于后续的epoll接口调用。当不在需要时,该描述符需要用close关闭。如果一个epoll实例的所有描述符都关闭了,则内核会销毁该实例,并释放相关资源。

epoll_create1,如果flags参数为0,则除了省略了size参数之外,它与epoll_create是相同的。如果flags参数不为0,则目前它只能是EPOLL_CLOEXEC,用于设置该描述符的close-on-exec(FD_CLOEXEC)标志。

这两个系统调用,成功时返回非负的文件描述符,失败时返回-1.

epoll_create是在2.6内核版本中引入,在glibc2.3.2开始支持;epoll_create1是在2.6.27内核版本中引入,在glibc2.9开始支持。

最初的epoll_create实现中,size参数告诉内核,调用者期望添加到epoll实例中的文件描述符数量。内核使用该信息作为初始分配内部数据结构空间大小的“提示”(如果调用者的使用超过了size,则内核会在必要的情况下分配更多的空间)。目前,这种“提示”已经不再需要了,内核动态的改变数据结构的大小,但是为了保证向后兼容性(新的epoll应用运行于旧的内核上),size参数还是要大于0。

2:epoll_ctl

  1. #include<sys/epoll.h>
  2.  
  3. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

该系统调用控制在epoll实例epfd上的执行动作。在epoll实例上注册、注销、更改文件描述符。

该调用会在描述符fd上执行op操作,op的值可以有:

EPOLL_CTL_ADD,在epoll实例epfd上注册文件描述符fd,并将事件event关联到fd。

EPOLL_CTL_MOD,更改fd上关联的事件为event

EPOLL_CTL_DEL,在epoll实例epfd上,删除(注销)描述符fd。event参数被忽略,可以为NULL(在2.6.9之前,即使event参数被忽略,它也不能为NULL。自2.6.9起,操作EPOLL_CTL_DEL上,event才可以为NULL。如果应用程序需要在2.6.9之前的内核上运行,则需要指定非NULL的event)。

event参数,描述了关联到fd上的事件对象。struct epoll_event的定义如下:

  1. typedef union epoll_data
  2. {
  3. void *ptr;
  4. int fd;
  5. uint32_t u32;
  6. uint64_t u64;
  7. } epoll_data_t;
  8.  
  9. struct epoll_event
  10. {
  11. uint32_t events; /* Epoll events */
  12. epoll_data_t data; /* User data variable */
  13. };

成员data字段由用户使用。监听事件触发后,data会被返回给用户。通常将event.data.fd设定为fd,这样就可以知道哪个文件描述符触发了事件。ptr成员可用来指定与fd相关的用户数据,但因为data是个union,所以,不能同时使用ptr和fd,因此,可以使用其他手段使得文件描述符和用户数据关联起来,比如放弃使用fd,而在ptr指向的用户数据中包含fd。

成员events是由下列值组成的位数组:

EPOLLIN,文件可读;

EPOLLOUT,文件可写;

EPOLLRDHUP (since Linux 2.6.17),流式套接字,对端关闭了连接,或者半关闭了写操作。该标志,在边沿触发模式下检测对端关闭时,是很有用的;

EPOLLPRI,读操作上有紧急数据;

EPOLLERR,文件描述符上发生了错误。epoll_wait始终监听该事件,无需再events上设置;

EPOLLHUP,文件描述符上发生了Hang up。epoll_wait始终监听该事件,无需再events上设置;

EPOLLET,置文件描述符为边沿触发模式,epoll上默认的行为模式是水平触发模式;

EPOLLONESHOT (since Linux 2.6.2),置文件描述符为一次性的(one-shot)。这意味着,当该文件上通过epoll_wait触发了一个事件之后,该文件描述符就被内部disable了,在epoll实例上不再报告该文件描述符上的事件了。用户必须用EPOLL_CTL_MOD调用epoll_ctl,以新的事件掩码再次注册该描述符。

epoll_ctl成功时返回0,失败返回-1.

3:epoll_wait

  1. #include <sys/epoll.h>
  2.  
  3. int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
  4.  
  5. int epoll_pwait(int epfd, struct epoll_event *events, int maxevents, int timeout, const sigset_t *sigmask);

epoll_wait系统调用在epoll实例epfd上等待事件的发生。成功返回时,events指向的内存空间,会记录触发的事件。maxevents告知内核events的大小。epoll_wait的返回值最大为maxevents, maxevents参数必须大于0.

当调用返回时,如果检测到事件,就将所有就绪的事件从内核事件表中复制到events指向的数组中。这个数组只用于输出epoll_wait检测到的就绪事件,而不像select和poll的数组参数那样,即用于传入用户注册的事件,又用于输出检测到的事件。

timeout参数指定epoll_wait的阻塞时间,单位为毫秒。epoll_wait会一直阻塞,直到:

某个文件描述符上触发了一个事件;

epoll_wait调用被信号中断;

超时时间到;

注意,timeout在内部会向上取整到系统时钟粒度,而且由于内核调度的原因,阻塞时间会有少量的延长。如果timeout为-1,意味着epoll_wait会一直阻塞;如果timeout为0,则即使没有事件发生,epoll_wait也会立即返回。

结构体epoll_event的定义见上,epoll_wait返回时,该结构中的data成员,与调用epoll_ctl(EPOLL_CTL_ADD, EPOLL_CTL_MOD)时的data一样。events成员包含触发的事件掩码。

epoll_wait和epoll_pwait之间的关系,类似于select和pselect之间的关系:epoll_pwait可以使应用程序安全的等待信号的发生。下面的语句:

  1. ready = epoll_pwait(epfd, &events, maxevents, timeout, &sigmask);

等价于下列语句的原子执行:

  1. sigset_t origmask;
  2.  
  3. sigprocmask(SIG_SETMASK, &sigmask, &origmask);
  4. ready = epoll_wait(epfd, &events, maxevents, timeout);
  5. sigprocmask(SIG_SETMASK, &origmask, NULL);

如果将sigmask置为NULL,则epoll_pwait等价于epoll_wait。

epoll_wait成功时返回准备好的文件描述符数,如果超时时间到了,还没有准备好的文件描述符,则返回0.出错时返回-1.

epoll_wait自2.6内核引入,glibc2.3.2开始支持;epoll_pwait自2.6.19内核引入,glibc2.6开始支持.

注意,如果某个线程阻塞于epoll_wait时,另一个线程向相同的epoll实例上添加了新的文件描述符,而且该描述符上的事件触发,则会导致原来阻塞于epoll_wait上的线程停止阻塞。

在select系统调用中,如果select上监听的描述符,在其他线程上被关闭了,则这种行为是未定义的。某些UNIX系统上,select会停止阻塞直接返回,并且将该描述符视为准备好的(接下来的IO操作会发生错误)。在Linux(以及其他系统)上,在其他线程上关闭该描述符对select无影响,epoll上的处理方式也一样。

在2.6.37之前,如果timeout参数大于(LONG_MAX/HZ)毫秒的话,则会被视为-1,也就意味着永远等待。所以,如果某系统上sizeof(long)等于4,HZ等于1000,则如果timeouts大于35.79的话,就会给视为无限等待。((2^31 – 1) /1000/1000/60
== 35.79)

三:示例

1:对于采用LT工作模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件。当应用程序下一次调用epoll_wait时,epoll_wait还会再次向应用程序通告此事件,直到该事件被处理。也就是说只要还有没有处理的事件就会一直通知。

而对于采用 ET工作模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件,因为后续的epoll_wait调用将不再向应用程序通知这一事件。可见,ET模式在很大程度上降低了同一个epoll事件被重复触发的次数,因此效率要比LT模式高。边沿触发需要一个不同的方式来写程序,通常利用非阻塞IO。并需要仔细检查EAGAIN。

下面的代码体现了LT和ET在工作方式上的差异。

  1. #define MAX_EVENT_NUMBER 1024
  2. #define BUFFER_SIZE 10
  3.  
  4. int setnonblocking( int fd )
  5. {
  6. int old_option = fcntl( fd, F_GETFL );
  7. int new_option = old_option | O_NONBLOCK;
  8. fcntl( fd, F_SETFL, new_option );
  9. return old_option;
  10. }
  11.  
  12. void addfd(int epollfd, int fd, bool enable_et)
  13. {
  14. struct epoll_event event;
  15. event.data.fd = fd;
  16. event.events = EPOLLIN;
  17. if( enable_et )
  18. {
  19. event.events |= EPOLLET;
  20. }
  21. epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event );
  22. setnonblocking(fd);
  23. }
  24.  
  25. void lt(struct epoll_event* events, int number, int epollfd, int listenfd )
  26. {
  27. char buf[ BUFFER_SIZE ];
  28. for ( int i = 0; i < number; i++ )
  29. {
  30. int sockfd = events[i].data.fd;
  31. if ( sockfd == listenfd )
  32. {
  33. struct sockaddr_in client_address;
  34. socklen_t client_addrlength = sizeof( client_address );
  35. int connfd = accept(listenfd, (struct sockaddr* )&client_address, &client_addrlength );
  36. addfd( epollfd, connfd, false );
  37. }
  38. else if ( events[i].events & EPOLLIN )
  39. {
  40. printf( "event trigger once\n" );
  41. memset( buf, '\0', BUFFER_SIZE );
  42. int ret = recv( sockfd, buf, BUFFER_SIZE-1, 0 );
  43. if( ret <= 0 )
  44. {
  45. close( sockfd );
  46. continue;
  47. }
  48. printf( "get %d bytes of content: %s\n", ret, buf );
  49. }
  50. else
  51. {
  52. printf( "something else happened \n" );
  53. }
  54. }
  55. }
  56.  
  57. void et( epoll_event* events, int number, int epollfd, int listenfd )
  58. {
  59. char buf[ BUFFER_SIZE ];
  60. for ( int i = 0; i < number; i++ )
  61. {
  62. int sockfd = events[i].data.fd;
  63. if ( sockfd == listenfd )
  64. {
  65. struct sockaddr_in client_address;
  66. socklen_t client_addrlength = sizeof( client_address );
  67. int connfd = accept(listenfd, (struct sockaddr* )&client_address, &client_addrlength );
  68. addfd( epollfd, connfd, true );
  69. }
  70. else if (events[i].events & EPOLLIN)
  71. {
  72. printf( "event trigger once\n" );
  73. while( 1 ) //循环读取数据,以确保将缓存中的数据全部读出
  74. {
  75. memset( buf, '\0', BUFFER_SIZE );
  76. int ret = recv( sockfd, buf, BUFFER_SIZE-1, 0 );
  77. if( ret < 0 )
  78. {
  79. //对于非阻塞IO,下面的条件成立表示数据已经全部读取完毕。
  80. //此后,epoll就能再次触发sockfd上的EPOLLIN事件。
  81. if( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) )
  82. {
  83. printf( "read later\n" );
  84. break;
  85. }
  86. close( sockfd );
  87. break;
  88. }
  89. else if( ret == 0 )
  90. {
  91. close( sockfd );
  92. }
  93. else
  94. {
  95. printf( "get %d bytes of content: %s\n", ret, buf );
  96. }
  97. }
  98. }
  99. else
  100. {
  101. printf( "something else happened \n" );
  102. }
  103. }
  104. }
  105.  
  106. int main( int argc, char* argv[] )
  107. {
  108. if( argc <= 2 )
  109. {
  110. printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );
  111. return 1;
  112. }
  113. const char* ip = argv[1];
  114. int port = atoi( argv[2] );
  115.  
  116. int ret = 0;
  117. struct sockaddr_in address;
  118. bzero( &address, sizeof( address ) );
  119. address.sin_family = AF_INET;
  120. inet_pton( AF_INET, ip, &address.sin_addr );
  121. address.sin_port = htons( port );
  122.  
  123. int listenfd = socket( PF_INET, SOCK_STREAM, 0 );
  124. assert( listenfd >= 0 );
  125.  
  126. ret = bind( listenfd, ( struct sockaddr* )&address, sizeof( address ) );
  127. assert( ret != -1 );
  128.  
  129. ret = listen( listenfd, 5 );
  130. assert( ret != -1 );
  131.  
  132. struct epoll_event events[ MAX_EVENT_NUMBER ];
  133. int epollfd = epoll_create( 5 );
  134. assert( epollfd != -1 );
  135. addfd( epollfd, listenfd, false);
  136.  
  137. while( 1 )
  138. {
  139. int ret = epoll_wait( epollfd, events, MAX_EVENT_NUMBER, -1);
  140. if ( ret < 0 )
  141. {
  142. printf( "epoll failure\n" );
  143. break;
  144. }
  145.  
  146. lt( events, ret, epollfd, listenfd);
  147. //et( events, ret, epollfd, listenfd);
  148. }
  149.  
  150. close( listenfd );
  151. return 0;
  152. }

2:即使使用ET模式,一个socket上的某个事件还是可能被触发多次。这在并发程序中就会引起一个问题。比如一个线程(或进程,下同)在读取完某个socket的数据后开始处理这此数据,而在数据的处理过程中该socket上又有新数据可读,此时另外一个线程被唤醒来读取这些新的数据(参见下面的代码)。于是就出现了两个线程同时操作一个socket的局面。这当然不是找们期望的。我们期望的是一个socket连接在任一时刻都只被一个线程处理。这一点可以使用epoll的EPOLLONESHOT事件实现。

对于注册了EPOLLONESHOT事件的文件描述符,操作系统最多触发其上注册的一个可读、可写或者异常事件,且只触发一次,除非我们使用epoll_ctl函数重置该文件描述符上注册的EPOLLONESHOT事件。这样,当一个线程在处理某个socket时.其他线程是不可能有机会操作该socket的。但反过来思考,注册了EPOLLONESHOT事件的socket一旦被某个线程处理完毕,该线程就应该立即重置这个socket上的EPOLLONESHOT事件,以确保这个socket下一次可读时,其EPOLLIN事件能被触发,进而让其他工作线程有机会继续处理这个socket。

下面的代码展示EPOLLONESHOT事件的使用:

  1. #define MAX_EVENT_NUMBER 1024
  2. #define BUFFER_SIZE 1024
  3. struct fds
  4. {
  5. int epollfd;
  6. int sockfd;
  7. };
  8.  
  9. int setnonblocking( int fd )
  10. {
  11. int old_option = fcntl( fd, F_GETFL );
  12. int new_option = old_option | O_NONBLOCK;
  13. fcntl( fd, F_SETFL, new_option );
  14. return old_option;
  15. }
  16.  
  17. void addfd( int epollfd, int fd, bool oneshot )
  18. {
  19. struct epoll_event event;
  20. event.data.fd = fd;
  21. event.events = EPOLLIN | EPOLLET;
  22. if( oneshot )
  23. {
  24. event.events |= EPOLLONESHOT;
  25. }
  26. epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event );
  27. setnonblocking( fd );
  28. }
  29.  
  30. void reset_oneshot( int epollfd, int fd )
  31. {
  32. struct epoll_event event;
  33. event.data.fd = fd;
  34. event.events = EPOLLIN | EPOLLET | EPOLLONESHOT;
  35. epoll_ctl( epollfd, EPOLL_CTL_MOD, fd, &event );
  36. }
  37.  
  38. void* worker( void* arg )
  39. {
  40. int sockfd = ( (fds*)arg )->sockfd;
  41. int epollfd = ( (fds*)arg )->epollfd;
  42. printf( "start new thread to receive data on fd: %d\n", sockfd );
  43. char buf[ BUFFER_SIZE ];
  44. memset( buf, '\0', BUFFER_SIZE );
  45. while( 1 )
  46. {
  47. int ret = recv( sockfd, buf, BUFFER_SIZE-1, 0 );
  48. if( ret == 0 )
  49. {
  50. close( sockfd );
  51. printf( "foreiner closed the connection\n" );
  52. break;
  53. }
  54. else if( ret < 0 )
  55. {
  56. if( errno == EAGAIN )
  57. {
  58. reset_oneshot( epollfd, sockfd );
  59. printf( "read later\n" );
  60. break;
  61. }
  62. }
  63. else
  64. {
  65. printf( "get content: %s\n", buf );
  66. sleep( 5 );//休眠5s,模拟数据处理过程
  67. }
  68. }
  69. printf( "end thread receiving data on fd: %d\n", sockfd );
  70. }
  71.  
  72. int main( int argc, char* argv[] )
  73. {
  74. if( argc <= 2 )
  75. {
  76. printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );
  77. return 1;
  78. }
  79. const char* ip = argv[1];
  80. int port = atoi( argv[2] );
  81.  
  82. int ret = 0;
  83. struct sockaddr_in address;
  84. bzero( &address, sizeof( address ) );
  85. address.sin_family = AF_INET;
  86. inet_pton( AF_INET, ip, &address.sin_addr );
  87. address.sin_port = htons( port );
  88.  
  89. int listenfd = socket( PF_INET, SOCK_STREAM, 0 );
  90. assert( listenfd >= 0 );
  91.  
  92. ret = bind( listenfd, ( struct sockaddr* )&address, sizeof( address ) );
  93. assert( ret != -1 );
  94.  
  95. ret = listen( listenfd, 5 );
  96. assert( ret != -1 );
  97.  
  98. epoll_event events[ MAX_EVENT_NUMBER ];
  99. int epollfd = epoll_create( 5 );
  100. assert( epollfd != -1 );
  101. addfd( epollfd, listenfd, false );//listenfd不能注册EPOLLONESHOT事件,否则应用程序只能处理一个客户连接,后续的客户连接请求将不再出发listenfd上的EPOLLIN事件。
  102.  
  103. while( 1 )
  104. {
  105. int ret = epoll_wait( epollfd, events, MAX_EVENT_NUMBER, -1 );
  106. if ( ret < 0 )
  107. {
  108. printf( "epoll failure\n" );
  109. break;
  110. }
  111.  
  112. for ( int i = 0; i < ret; i++ )
  113. {
  114. int sockfd = events[i].data.fd;
  115. if ( sockfd == listenfd )
  116. {
  117. struct sockaddr_in client_address;
  118. socklen_t client_addrlength = sizeof( client_address );
  119. while((connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength )) > 0)
  120. {
  121. addfd( epollfd, connfd, true );
  122. }
  123. }
  124. else if ( events[i].events & EPOLLIN )
  125. {
  126. pthread_t thread;
  127. fds fds_for_new_worker;
  128. fds_for_new_worker.epollfd = epollfd;
  129. fds_for_new_worker.sockfd = sockfd;
  130. pthread_create( &thread, NULL, worker, ( void* )&fds_for_new_worker );
  131. }
  132. else
  133. {
  134. printf( "something else happened \n" );
  135. }
  136. }
  137. }
  138.  
  139. close( listenfd );
  140. return 0;
  141. }

从工作线程函数worker来看,如果一个工作线程处理完某个socket上的一次请求(用休眠5s来模拟这个过程)之后,又接收到该socket上新的客户请求,则该线程将继续为这个socket服务。并且因为该socket上注册了EPOLLONESHOT事件,其他线程没有机会接触这个socket,如果工作线程等待5s后仍然没收到该socket上的下一批客户数据,则它将放弃为该socket服务。同时,它调用reset_oneshot函数来重置该socket的注册事件,这将使epoll有机会再次检测到该socket的EPOLLIN事件,进而使得其他线程有机会为该socket服务。

由此看来,尽管一个socket在不同时间能被不同的线程处理,但同一时刻肯定只有一个线程在为它服务。这就保证了连接的完整性,从而避免了很多可能的竟态条件。

参考:

http://blog.csdn.net/Al_xin/article/details/39037037

man手册

epoll简介(一)的更多相关文章

  1. epoll简介

    1.epoll简介 epoll是I/O事件通知工具,与select/poll相比,epoll最大的好处在于它不会随着监听fd数目的增长而效率降低.epoll API既可以用作edge触发的接口,也可以 ...

  2. Epoll简介以及例子

    第一部分:Epoll简介 问题 :  Select,Poll和Epoll的区别 答案 : Epoll和Select的区别 1. 遍历方式的区别.select判断是否有事件发生是遍历的,而epoll是事 ...

  3. 基本I/O模型与Epoll简介

    5种基本的I/O模型:1)阻塞I/O ;2)非阻塞I/O; 3)I/O复用(select和poll);4)信号驱动I/O(SIGIO);5)异步I/O(POSIX.1的aio_系列函数). 操作系统中 ...

  4. epoll简介 与 UDP server的实现

    Abstractepoll是Linux内核为处理大批量句柄而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著减少程序在大量并发连接中只有少量活跃的情况下的系 ...

  5. select、poll、epoll简介

    epoll跟select都能提供多路I/O复用的解决方案.在现在的Linux内核里有都能够支持,其中epoll是Linux所特有,而select则应该是POSIX所规定,一般操作系统均有实现 sele ...

  6. epoll简介(二)

    一:多路复用的举例 以一个生活中的例子来解释: 假设你在大学中读书,要等待一个朋友(数据)来访(要读),而这个朋友只知道你在A号楼(socket集合),但是不知道你具体住在哪里,于是你们约好了在A号楼 ...

  7. I/O多路复用之select,poll,epoll简介

    一.select 1.起源 select最早于1983年出现在4.2BSD中(BSD是早期的UNIX版本的分支). 它通过一个select()系统调用来监视多个文件描述符的数组,当select()返回 ...

  8. linux下select/poll/epoll机制的比较

    select.poll.epoll简介 epoll跟select都能提供多路I/O复用的解决方案.在现在的Linux内核里有都能够支持,其中epoll是Linux所特有,而select则应该是POSI ...

  9. epoll ET模式陷阱分析

    0. 前言 这篇文章主要记录在使用epoll实现NIO接入时所遇到的问题. 1. epoll简介 epoll是Linux下提供的NIO,其主要有两种模式,ET(Edge trige)和LT(Level ...

随机推荐

  1. Oracle 查询库中所有表名、字段名、表名说明、字段名说明(原创)

    查询所有表名:select t.table_name from user_tables t;查询所有字段名:select t.column_name from user_col_comments t; ...

  2. 洛谷P1315 [NOIP2011提高组Day2T3] 观光公交

    P1315 观光公交 题目描述 风景迷人的小城Y 市,拥有n 个美丽的景点.由于慕名而来的游客越来越多,Y 市特意安排了一辆观光公交车,为游客提供更便捷的交通服务.观光公交车在第 0 分钟出现在 1号 ...

  3. web前端学习(四)JavaScript学习笔记部分(5)-- 事件流详解

    1.JS事件详解-事件流 1.1.事件流 1.事件流: 描述的是在页面中接受事件的顺序 2.事件冒泡: 由最具体的元素接收,然后逐级上传播至最不具体的节点(文档) 3.事件捕获: 最不具体的节点先接收 ...

  4. Mac 下终端命令行之基本命令总结(持续更新)

    最近用Mac做一些开发,用到了一些命令行的内容,先将常用的命令行进行总结.由于会不断的用到新的,所以将会持续的总结进来.每一个命令行的使用可能都会比较复杂,我只会总结最常用的使用方法. echo命令 ...

  5. 第三方数据库管理工具Navicat使用教程

    一.Navicat Premium是一个功能强大的第三方数据库管理工具,可以连接管理MySQL.Oracle.PostgreSQL.SQLite 及 SQL Server数据库. 使用Navicat软 ...

  6. 无线传感网络协议——Smart Mesh IP

    前言: SmartMesh IP 专为实现 IP 兼容性而设计,并基于 6LoWPAN 和 802.15.4e 标准.SmartMesh IP 产品线实现了网络适应性.可靠性和可扩展性水平,并拥有高级 ...

  7. Liferay如何连接本地的数据库

    Liferay自带的数据库非常迷你,一般就是玩玩的. 在真实的开发过程中,我们往往需要把它与我们本地的数据库相连. 有3中方法,我在这里就只介绍我自己最喜欢的方法啦.连的是mysql 1.在Lifer ...

  8. php表单传值--GET和POST

    一.       传值 1.    传值/接收方法: 1)        GET(5种方式!) a)       表单Form: method = ‘get’   GET接收数据方式: b)      ...

  9. R是用于统计分析、绘图的语言和操作环境

    R是一套完整的数据处理.计算和制图软件系统.其功能包括:数据存储和处理系统:数组运算工具(其向量.矩阵运算方面功能尤其强大):完整连贯的统计分析工具:优秀的统计制图功能:简便而强大的编程语言:可操纵数 ...

  10. R语言因子

    R语言因子 因子是它们用于将数据进行分类并将其存储为级别的数据对象.它们可以同时存储字符串和整数.它们在具有唯一值的有限数目的列是有用的. 例如,"male, "Female&qu ...