以下是《Linux高性能服务器编程》对EPOLLONESHOT的描述:

对于注册了EPOLLONESHOT的文件描述符,操作系统最多触发其上注册的一个可读、可写或者异常的事件,且只触发一次,除非我们使用epoll_ctl函数重置该文件描述符上注册的EPOLLONESHOT事件。

乍一看会以为EPOLLONESHOT不就是ET模式相对于LT模式的区别,仔细阅读之后发现原来ET和EPOLLONESHOT是不同的。

区别时:ET是可读、可写、异常事件中的每一种只能被触发一次,就是说这三种不同的事件都是能被触发的只不过只能一次而已。而EPOLLONESHOT是所有的这些事件总共只能被触发一次。

再仔细阅读,发现书上在介绍EPOLLONESHOT时说其是为了应对ET模式下同种事件可能会被触发多次的情况。这就引发了一个问题。那就是:

ET模式能被同种事件触发多次吗?什么情况才会多次触发呢?

思路一:数据之间时间间隔过大?

验证:发送大量数据,但是并没有出现ET模式多次触发的情况。

思路二:和接受缓冲区有关?默认的接受缓冲区太大使用setsockopt的SO_RCVBUF选项了调小接受缓冲区试试。

验证:调小缓冲区后果然出现了ET多次触发得情况。

服务端程序:

 #include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <pthread.h>#define MAX_EVENT_NUMBER 1024
#define BUFFER_SIZE 2500
//将文件描述符设置成非阻塞的
int setnonblocking( int fd )
{int old_option = fcntl( fd, F_GETFL );int new_option = old_option | O_NONBLOCK;fcntl( fd, F_SETFL, new_option );return old_option;
}
//将文件描述符的EPOLLIN注册到epollfd指示的epoll内核事件表中,参数enable_et指定是否对fd启动ET模式。
void addfd( int epollfd, int fd, bool enable_et )
{//event用于描述事件类型(读入、读出、是否LET、)。epoll_event event;event.data.fd = fd;event.events = EPOLLIN;if( enable_et ){event.events |= EPOLLET;}epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event );setnonblocking( fd );
}/*就绪事件就存在这个events数组里面了,number是事件数,遍历number个事件,确定其类型并处理就好了*/
void et( epoll_event* events, int number, int epollfd, int listenfd )
{char buf[ BUFFER_SIZE ];for ( int i = 0; i < number; i++ ){int sockfd = events[i].data.fd;if ( sockfd == listenfd )/*有新的连接*/{struct sockaddr_in client_address;socklen_t client_addrlength = sizeof( client_address );int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );addfd( epollfd, connfd, true );}else if ( events[i].events & EPOLLIN ){printf( "et event trigger once\n" );while( 1 ){memset( buf, '\0', BUFFER_SIZE );int ret = recv( sockfd, buf, BUFFER_SIZE-1, 0 );if( ret < 0 ){if( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) ){printf( "read later\n" );break;}close( sockfd );break;}else if( ret == 0 ){close( sockfd );}else{//printf( "get %d bytes of content: %s\n", ret, buf );printf("get %d byte data,………………\n",ret);}}}else{printf( "something else happened \n" );}}
}int main( int argc, char* argv[] )
{if( argc <= 2 ){printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );return 1;}const char* ip = argv[1];int port = atoi( argv[2] );int ret = 0;struct sockaddr_in address;bzero( &address, sizeof( address ) );address.sin_family = AF_INET;inet_pton( AF_INET, ip, &address.sin_addr );address.sin_port = htons( port );int listenfd = socket( PF_INET, SOCK_STREAM, 0 );assert( listenfd >= 0 );/*----------这一段是自己添加的用于修改接受缓冲区的大小----------*//*(1)此处我将缓冲区大小设为1250,实际上系统会将其加倍即实际缓冲区大小是2500,但是接收数据的时候还是1250字节,Why?(2)缓冲区也不是随便设的在/proc/sys/net/ipv4/tcp_rmem中有[min default max]的信息。我的环境默认的接受缓冲区大小是87380字节,实际上最小限制是2304字节。*/int recvbuf = 1250;int len = sizeof(int);setsockopt(listenfd, SOL_SOCKET, SO_RCVBUF, &recvbuf, sizeof(int));getsockopt(listenfd, SOL_SOCKET, SO_RCVBUF, &recvbuf, (socklen_t*)&len);printf("the receive buffer size after settting is %d\n", recvbuf);ret = bind( listenfd, ( struct sockaddr* )&address, sizeof( address ) );assert( ret != -1 );ret = listen( listenfd, 5 );assert( ret != -1 );epoll_event events[ MAX_EVENT_NUMBER ];int epollfd = epoll_create( 5 );assert( epollfd != -1 );addfd( epollfd, listenfd, true );while( 1 ){   int ret = epoll_wait( epollfd, events, MAX_EVENT_NUMBER, -1 );if ( ret < 0 ){printf( "epoll failure\n" );break;}et( events, ret, epollfd, listenfd );}close( listenfd );return 0;
}

客户端程序:

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <poll.h>
#include <fcntl.h>
#include <iostream>
#include <string>
using namespace std;int main(int argc, char* argv[])
{if (argc <= 2){printf("usage: %s ip_address port_number\n", basename(argv[0]));return 1;}const char* ip = argv[1];int port = atoi(argv[2]);struct sockaddr_in server_address;bzero(&server_address, sizeof(server_address));server_address.sin_family = AF_INET;inet_pton(AF_INET, ip, &server_address.sin_addr);server_address.sin_port = htons(port);int sockfd = socket(PF_INET, SOCK_STREAM, 0);assert(sockfd >= 0);if (connect(sockfd, (struct sockaddr*)&server_address, sizeof(server_address)) < 0){printf("connection failed\n");close(sockfd);return 1;}else {/*此处发送10000字节的数据*/char str[10000];memset(str, 'a', 10000);send(sockfd, str,strlen(str),0);}close(sockfd);return 0;
}

结果如下:

综上所述:EPOLL的ET模式即时针对同一种事件也是有可能引起多次触发的。

对于读事件系统读缓冲区溢出时就会引起多次触发;对于写事件系统写缓冲区溢出时同样会引起多次触发。

接下来说说这个EPOLLONESHOT:

作用:保证同一时刻一个socket仅被一个线程处理。(不同时刻可以有不同线程处理)

前面说了eppll 即使使用ET模式,一个socket上的某个事件还是可能被触发多次。如果采用线程池的方式来处理事件,可能一个socket同时被多个线程处理。例如:在线程池技术中每次有触发事件就将其添加到任务队列中,线程池中的线程去竞争任务队列里面的任务(生产者/消费者模式)。显然如果某个线程在读取完某个socket上的数据后开始处理这些数据,而在数据的处理过程中该socket上又有新的可读数据(EPOLLIN再次被触发),此时另外一个线程被唤醒在读取这些新的数据。于是就出现了两个线程同时操作一个socket的局面。显然这不是我们所期望的。

EPOLL的EPOLLONESHOT事件,使一个socket连接任何时刻都只被一个线程所处理对于注册了EPOLLONESHOT事件的socket,操作系统最多触发其上注册的一个可读、可写、或者异常事件,且只触发一次。这样,当一个线程在处理某个socket时,其他线程是不可能有机会操作该socket的,但反过来,注册了EPOLLONESHOT事件的socket一旦被某个线程处理完毕,该线程就应该立即重置这个socket上的EPOLLONESHOT事件,以确保这个socket下一次可读,其EPOLLIN事件能被触发,其他工作线程有机会继续处理这个socket。由此看来,尽管一个socket在不同时刻可能被不同的线程处理,但同一时刻肯定只有一个线程再为它服务,这就保证了连接的完整性,从而避免了很多可能的竞态条件。

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

例子:

epoll_event event;

event..data.fd=fd;

evnet.events=EPOLLIN | EPOLLT |EPOOLLONESHOT

epoll_ctl( epollfd,EPOLL_CTL_ADD , fd , &event );// 第一次添加

epoll_wait 返回, 处理fd的读事件,一直读一直读,读到没有数据 ( errno==EAGAIN) ,这时才重置fd上的事件

epoll_event event;

event..data.fd=fd;

evnet.events=EPOLLIN | EPOLLT |EPOOLLONESHOT

epoll_ctl( epollfd,EPOLL_CTL_MOD , fd , &event );

EPOLLONESHOT及其引发的EPOLL在ET能被多次触发吗?相关推荐

  1. EPOLL在ET模式下会被触发多次么?

    前几天和同学一起讨论EPOLLONESHOT的作用,它的功能是这样的: 对于注册了EPOLLONESHOT事件的文件描述符,操作系统最多触发其上注册的包括可读,可写,错误中的一个,且只触发一次 刚一看 ...

  2. 【Linux学习】epoll详解

    什么是epoll epoll是什么?按照man手册的说法:是为处理大批量句柄而作了改进的poll.当然,这不是2.6内核才有的,它是在2.5.44内核中被引进的(epoll(4) is a new A ...

  3. linux网络编程--select/poll/epoll 详解

    目录 参考链接 epoll函数 close epoll event EL/LT ET Edge Trigger 边沿触发工作模式 LT Level Trigger 水平触发工作模式 epoll 源码解 ...

  4. I/O多路复用:select、poll和epoll详解

    I/O多路复用 I/O复用使得程序能同时监听多个文件描述符,这对提高程序的性能至关重要.通常,网络程序在下列情况下需要使用I/O复用技术: 服务端程序要同时处理多个 socket.比如非阻塞 conn ...

  5. 读过的最好的epoll讲解

    首先我们来定义流的概念,一个流可以是文件,socket,pipe等等可以进行I/O操作的内核对象. 不管是文件,还是套接字,还是管道,我们都可以把他们看作流. 之后我们来讨论I/O的操作,通过read ...

  6. Linux: I/O多路转接之epoll(有图有代码有真相!!!)

    一.基本概念 epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下 ...

  7. linux——select、poll、epoll

    文章目录 1.多路I/O转接服务器 2.select 3.select代码 4.poll 5.epoll 5.1 基础API 5.3 epoll代码 5.4 边沿触发和水平触发 5.4.1 水平出发L ...

  8. epoll 系列系统调用

    epoll是linux 特有的I/O复用函数,它在实现和使用上和select ,poll有很大的差异!首先epoll使用一组函数来完成任务,而不是单个函数.其实epoll吧用户关心的文件描述符的事件放 ...

  9. linux socket epoll

    什么是epoll epoll是什么?按照man手册的说法:是为处理大批量句柄而作了改进的poll.当然,这不是2.6内核才有的,它是在2.5.44内核中被引进的(epoll(4) is a new A ...

  10. Linux select/poll/epoll

    参考地址:https://www.jianshu.com/p/fe54ca4affe8 本文讨论的背景是Linux环境下的network IO. 一. 概念说明 在进行解释之前,首先要说明几个概念: ...

最新文章

  1. 【Win32汇编】复制字符串
  2. php使mysql显示错误_如何针对依赖用户输入的长查询在PHP中显示MySQL错误?
  3. 我从Kaggle机器学习竞赛中获得的经验
  4. java实现冗余校验_循环冗余校验_循环冗余校验码计算_循环冗余校验 java实现(6)...
  5. Kafka 设计与原理详解(一)
  6. vb教材笔记_vb学习笔记
  7. php 修改文件的权限_授予PHP写入文件和文件夹的权限
  8. Vista 陪我过周末
  9. 测试用例的设计-面试常见问题(基础篇)
  10. windows11安装日语输入法(添加输入法)
  11. 牛客习题总结38(7月13日)
  12. 如何使用计算机处理文件夹,电脑打开某些文件夹提示引用了不可用位置怎么解决[多图]...
  13. 带宽,速率,吞吐量区别
  14. php curl登录,php curl实现第三方帐号登录
  15. 20180529-A · Comic book characters · ggplot2 geom_bar geom_text 柱状图 条形图 图例 · R 语言数据可视化 案例 源码
  16. 【TA-霜狼_may-《百人计划》】图形4.5 DoF景深基础
  17. snidel 2014春夏新品 画册款切换材质连衣裙
  18. CentOS离线安装Tomcat
  19. 有关华为的七大猜想:或在国内屈居老二
  20. Spring之——c3p0配置详解

热门文章

  1. 力扣-203 移除链表元素
  2. Git — 解决“requested upstream branch ‘origin/master‘ does not exist“
  3. 1081 检查密码 (15 分)—PAT (Basic Level) Practice (中文)
  4. 关于IEnumeratorT泛型枚举器 和 IEnumerableT
  5. 并查集(2)-按秩合并和路径压缩
  6. 3.求子数组的最大和
  7. ymPrompt.js消息提示组件 详解 .
  8. Ilist 和list的区别归纳总结
  9. HCIE-Security Day30:IPSec:实验(五)配置基于路由的IPSec PN(采用预共享密钥认证)
  10. H3C DHCP实验