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

时间:2022-09-09 09:11:03

前几天和同学一起讨论EPOLLONESHOT的作用,它的功能是这样的:

对于注册了EPOLLONESHOT事件的文件描述符,操作系统最多触发其上注册的包括可读,可写,错误中的一个,且只触发一次

刚一看感觉EPOLLONESHOT咋么就是ET模式相对于LT模式的区别,反复理解之后发现原来ET和ONESHOT的区别是ET只是可读可写或错误类的某一种事件类型只能被触发一次,而不同种类的事件类型却可以触发不止一次。难道仅仅是这个区别么,如果只是这样,我感觉给给事件类型加个ONESHOT还不如只用ET呢,好像这样也并没什么大问题。

又经过仔细读之后,我发现书上在介绍EPOLLONESHOT时说其是为了应对ET模式下同种事件可能会被触发多次的情况。这下我明白了ONESHOT的作用了,但是我却更加奇怪,ET模式下咋么会同种事件被触发多次呢?
后来我想会不会是发送方发送的数据中由于一些原因导致其中的一部分和另一部分发送来的时间有很大(注意这个很大也不是很大,你懂得)的间隔.那么咋样才可能出现这种情况呢,当然你每次发送的数据越大出现这种几率的可能也就越大,所以为此我写了代码进行了测试,但是发现测了好多也没出现ET被触发多次的现象
最后我想了下会不会是因为系统默认的接收缓冲区给的太大了?于是我将缓冲区改为500字节大小,然后send一次send100000数据,果不其然,这次ET模式下,可读被触发了40多次

具体测试情况如下:
server端

#include<iostream>
#include<vector>
#include<algorithm>
#include<numeric>
#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<sys/epoll.h>
#include<pthread.h>
using namespace std;

#define MAX_EVENT_NUMBER 1024
#define BUFFER_SIZE 10
int SUM = 0;

//设置文件描述符为非阻塞
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;

}


//添加fd到epoll内核事件表并选择是否开启ET模式
void addfd(int epoll_fd,int fd,bool enable_et)
{
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN | EPOLLERR;
if(enable_et)
{
//开启et模式
event.events |= EPOLLET;
}

epoll_ctl(epoll_fd,EPOLL_CTL_ADD,fd,&event);
setnonblocking(fd);

}


//ET模式
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);

//xiugai
int on = 500;
//设置接收缓冲区大小为500
setsockopt(connfd,SOL_SOCKET,SO_RCVBUF,(void *)&on,sizeof(int));
addfd(epollfd,connfd,true);
}
else if(events[i].events & EPOLLIN)
{
SUM++;
while(1)
{
bzero(buf,BUFFER_SIZE);
int ret = recv(sockfd,buf,BUFFER_SIZE-1,0);
if(ret < 0)
{
//对于非阻塞I/O,下面条件成立表示数据已经全部读取完毕,此后epoll就能再一次触发sockfd上的EPOLLIN事件,以驱动下次读操作
if((errno == EAGAIN) || (errno == EWOULDBLOCK))
{
cout<<"read later\n";
break;
}
close(sockfd);
break;
}
else if(ret == 0)
{
close(sockfd);
}
else
{
cout<<"get "<<ret<<" bytes of content "<<buf<<endl;

}
}

cout<<"times:"<<SUM<<endl;
}
else
{
cout<<"something else happened\n";
}
}
}


int main(int argc,char **argv)
{
if(argc <= 2)
{
cout<<"参数错误"<<endl;
}
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);

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)
{
cout<<"epoll failure"<<endl;
break;
}

//使用et模式
et(events,ret,epollfd,listenfd);

}
close(listenfd);
return 0;
}

client端

#include<iostream>
#include<vector>
#include<algorithm>
#include<numeric>
#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>
using namespace std;
int main(int argc,char **argv)
{
if(argc <= 2)
{
cout<<"您输入的参数有误";
return 1;
}

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)
{
cout<<"error"<<endl;
}

else
{
string str1(100000,'a');


int ret = send(sockfd,str1.c_str(),str1.size(),0);

cout<<"ret"<<ret<<endl;

}

sleep(10);
return 0;
}

服务器运行结果截图
EPOLL在ET模式下会被触发多次么?

从图中可以看出EPOLLIN被触发了48次,所以我们就知道了EPOLL的ET模式会在接收缓冲区溢出时,可能发生被触发多次的情况