C++ epoll\u等待返回EPOLLOUT,即使带有EPOLLET标志
我正在边缘触发模式下使用LinuxEPoll。 每次传入新连接时,我都使用EPOLLIN | epolout | EPOLLET标志将文件描述符添加到epoll。我的第一个问题是:在epoll_wait返回后,检查每个就绪文件描述符发生何种事件的正确方法是什么?我的意思是,我看到一些示例代码,例如第124行中的代码,如下所示:C++ epoll\u等待返回EPOLLOUT,即使带有EPOLLET标志,c++,c,linux,networking,epoll,C++,C,Linux,Networking,Epoll,我正在边缘触发模式下使用LinuxEPoll。 每次传入新连接时,我都使用EPOLLIN | epolout | EPOLLET标志将文件描述符添加到epoll。我的第一个问题是:在epoll_wait返回后,检查每个就绪文件描述符发生何种事件的正确方法是什么?我的意思是,我看到一些示例代码,例如第124行中的代码,如下所示: for (int i = 0; i < n; i++) { //... if (events & (EPOLLIN | EPOLLERR))
for (int i = 0; i < n; i++) {
//...
if (events & (EPOLLIN | EPOLLERR)) {
if (fd == lfd) {
handleAccept(efd, fd);
} else {
handleRead(efd, fd);
}
} else if (events & EPOLLOUT) {
if (output_log)
printf("handling epollout\n");
handleWrite(efd, fd);
} else {
exit_if(1, "unknown event");
}
}
它似乎坚持了我的想法,一个接一个地检查所有的EPOLLERR、EPOLLIN、EPOLLOUT事件。
然后,我在应用程序中执行与nginx相同的操作。但我在实验后意识到:如果我使用EPOLLIN | epolout | EPOLLET标志将文件描述符添加到epoll中,并且我没有填充输出缓冲区,那么在epoll | u wait返回后,由于一些数据到达,并且这个fd变得可读,我总是会设置epolout标志,因此会调用冗余写入| u处理程序,这不是我所期望的
我做了一些搜索,发现这种情况确实存在,而不是由我的应用程序中的任何错误引起的。根据最受欢迎的投票结果显示:
有一点相关:如果您注册EPOLLIN和EPOLLOUT事件,并且假设从未填充发送缓冲区,那么每次触发EPOLLIN时,您仍然会在epoll_wait返回的事件中设置EPOLLOUT标志-有关更详细的说明,请参阅
答案中的链接是:
这并不意味着有一个ePloot“事件”,它只是意味着一条信息
被触发(由套接字变得可读)以便获得状态
更新。理论上,该程序不需要被告知ePloot
这里(应该假设套接字已经可写),但是
没什么害处
到目前为止,我对epoll边缘触发模式的理解是:
- 当被监视的任何fd的状态发生变化时,例如从“无”变为“读取”->“可读”或“缓冲区已满”->“缓冲区可以写入”,epoll_等待返回
- epoll_wait可能会为就绪列表中的每个fd返回一个或多个事件(标志)
- sturct epoll_event.events字段中的标志指示此fd的当前状态。即使我们不填充输出缓冲区,当epoll_wait返回时也会设置EPOLLOUT标志,因为fd的当前状态是可写的
epoll\u wait()
。并且,当然,只要accept(),读和写端都可能已解锁
连接、读和写突然成为可能,因此您会得到一个“事件”EPOLLIN | epolout
我真的没有想到,epoll\u wait()
总是传递整个当前状态,而不仅仅是状态发生变化的部分——感谢您澄清了这一点。更清楚的是,epoll\u wait()
将不会返回fd,除非该套接字上发生了更改,但如果发生了更改,它将返回表示当前状态的所有标志。因此,我发现自己盯着一个EPOLLIN | epolout
事件流,想知道为什么它会声称存在“输出”你的回答是正确的:它只是告诉我输出端仍然是可写的
“我是否应该维护一个标志…”是的,但我可以想象,在除最琐碎的情况外的所有情况下,您可能最终都会为您的读者/作者维护至少一点“我当前是否被阻止”状态。例如,如果您希望以不同于数据到达方式的顺序处理数据(例如,将响应优先于请求,以使您的服务器更能抵抗过载)您必须立即放弃让I/O驱动一切的简单性。在编写的特定情况下,epoll根本没有足够的信息在“正确”位置通知您时间。一旦你接受一个连接,就会有一个事件说“你现在可以写了”——但是如果你是一个服务器,不可能已经收到客户端的请求,那么你可能没有什么要写的。epoll只是不知道你是否有要写的东西,所以你总是不得不要么写要么写“无关”事件,或保持自己的状态
除了最简单的情况外,Socket文件描述符最终是处理I/O事件的信息不足,所以如果您喜欢的话,总是必须将一些数据结构与它关联起来,或者对象。因此,我的C++看起来像:
nAwake = epoll_wait(epollFd, events, 100, milliseconds);
if(nAwake < 0)
{
perror("epoll_wait failed");
assert(false);
}
for(int iSocket=0; iSocket < nAwake; ++iSocket)
{
auto This = static_cast<Eventable*>(events[iSocket].data.ptr);
auto eventFlags = events[iSocket].events;
fprintf(stderr, "%s event on socket [%d] -> %s\n",
This->ClassName(), This->fd, DumpEvent(eventFlags));
This->Event(eventFlags);
}
nAwake=epoll\u wait(epollFd,事件,100毫秒);
if(nAwake<0)
{
perror(“epoll_等待失败”);
断言(假);
}
对于(int-iSocket=0;iSocket%s上”\n,
This->ClassName(),This->fd,DumpEvent(eventFlags));
此->事件(eventFlags);
}
<> > <代码> Enviaby是一个C++类(或派生类),它具有决定如何处理标志EPOLL传递的所有状态。(当然,这是让内核存储指向C++对象的指针,需要一个VE的设计。
nAwake = epoll_wait(epollFd, events, 100, milliseconds);
if(nAwake < 0)
{
perror("epoll_wait failed");
assert(false);
}
for(int iSocket=0; iSocket < nAwake; ++iSocket)
{
auto This = static_cast<Eventable*>(events[iSocket].data.ptr);
auto eventFlags = events[iSocket].events;
fprintf(stderr, "%s event on socket [%d] -> %s\n",
This->ClassName(), This->fd, DumpEvent(eventFlags));
This->Event(eventFlags);
}