epoll三种工作模式
水平触发模式 LT
之前伪代码的问题。
假设有100个数据,但是某次读只能读50个字节。下一次再读50个字节。
- 只要fd对应的缓冲区有数据,即判断缓冲区
- epoll_wait返回
- 返回的次数与发送的数据次数没有关系
用户虽然把数据收下来了,但是可能不一定知道epoll_wait返回了几次。
假设设置的读缓冲区比较小,就会出现上述分两次读的情况。
- 打个比方:老师告诉你要做作业,“每次”都会提醒你下,你语文要做了,数学要做了,英语要做了。这里的老师就好比内核,会督促你把作业都做完。
- 和边沿模式不同,老师就提醒你一次,语文要做哦。你只做了成语判断,还有阅读分析没做,但是她不会管你。一会儿可能数学作业要布置下来了,你只做了选择题,大题可能没做。
- LT支持block和非block方式。这种模式,内核会告诉你一个fd是否就绪,如果你不做任何操作,内核还是会继续通知你,所以这种模式编程出错可能性要小一点。
边沿触发模式-ET
- fd --默认具有阻塞属性
- Client Send Data to Server
- 发一次数据,server的epoll_wait返回一次
- 不再乎数据是否读完,客户端发一次,就返回一次
- “绞肉机”里面剩下的肉需要新的肉塞进去才能出来
- 貌似不合理却合理:epoll_wait调用次数越多,系统开销越大。一般不单独使用
- 存在即合理:epoll_wait调用次数越多,系统的开销越大。
- 不单独用,配合非阻塞方式
- 如果读不完
- while(recv()); 内核缓冲区没有数据,数据读完之后会阻塞
- 解决阻塞问题
- 设置非阻塞 -fd (边沿非阻塞模式)
//修改监听的方式
tmp.events = EPOLL_IN | EPOLL_ET; //只增加一个EPOLL_ET宏
tmp.events.cfd = connfd; //accept进来的描述符
边沿非阻塞触发 ET+NO-BLOCK
- 效率高,高速工作方式,只支持no-block socket。当fd从not ready–>ready时,内核通过epoll通知你。然后假设你已知晓该事件,并不会再为那个fd提供更多的就绪通知。如果一直不对这个fd做I/O操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)
- 如何解决非阻塞
- open()
- 设置flags
- 必须 O_WDRW | O_NOBLOCK //变成非阻塞
- 适应终端文件: /dev/tty
- fcntl
- fcnt方式设置cfd非阻塞模式
- open()
//关键:设置cfd为非阻塞模式
int flag = fcntl(cfd, F_GETFL);
flag | = O_NOBLOCK;
fcntl(cfd, F_SETFL, flag);
struct epoll_event tmp_evt;
tmp_evt.event = EPOLLIN|EPOLLET;
tmp_evt.data.fd = cfd; //accept返回后的描述符
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &tmp_evt);
/*... ... ...*/
//循环读取数据
while((recv_len = recv(fd, buf, sizeof(buf), 0))>0)
{
write(STDOUT_FILENO, buf, recv_len ); //打印到终端
send(fd, buf, len ,0); //返回给客户端
}
if(len == 0)
{
printf("客户端断开连接\n");
epoll_ctl(expd, EPOLL_CTL_DEL, fd, NULL);
close(fd);
}else if(len = -1){
printf("error\n");
exit(1);
}
上述代码在读完数据后会退出:
- 原因
- 数据读完之后,又去读,但是现在是非阻塞情况,又去强行读缓冲区
- 强行读了一个没有数据的缓冲区(fd),导致返回-1
- 判断 error == EAGAIN
if(len == -1)
{
if(error == EAGAIN)
{
printf("缓冲区数据已读完");
}
else
{
printf("error\n");
exit(1);
}
}
文件描述符突破1024限制
- select 需要编译内核才能突破1024限制
- poll和epoll 可以突破1024
- poll 内部链表
- epoll红黑树
- 查看文件描述符上线
- cat /proc/sys/fs/file-max
- 和机器硬件相关
- 通过配置文件修改上限值
- /etc/security/limits.conf
- soft nofile 8000 //不能超过limits上限
- hard nofile 8000
- 重启或注销才生效
- ulimit -n 3000
epoll反应堆模型
在struct epoll_event结构体中的
struct epoll_event{
__uint32_t events; /*Epoll events*/
epoll_data_t data; /*User data variable*/
}
typedef union epoll_data{
void *ptr;
int fd; //epoll_wait返回后利用response_event[i].data.fd来判断某个fd就绪
uint32_t u32;
uint64_t u64;
}epoll_data_t;
//fake code-----------------------------------------------------------------------
rets=epoll_wait(gefd, response_events, max_events, -1);
for(int i=0;i<rets;i++)
{
...
response_events.data.fd = connfd; //利用的epoll_event.data.fd来帮助我们判断,
...
}