gpt4 book ai didi

c++ - epoll_wait 即使带有 EPOLLET 标志也返回 EPOLLOUT

转载 作者:行者123 更新时间:2023-12-03 07:03:52 30 4
gpt4 key购买 nike

我在边缘触发模式下使用 linux epoll。
每次有新连接传入时,我都会使用 EPOLLIN|EPOLLOUT|EPOLLET 标志将文件描述符添加到 epoll。我的第一个问题是:在 epoll_wait 返回后,检查每个就绪文件描述符发生哪种事件的正确方法是什么?我的意思是,我看到了一些示例代码,例如来自 https://github.com/yedf/handy/blob/master/raw-examples/epoll-et.cc第 124 行这样做:

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");
}
}

引起我注意的是:它使用“if and else if and else”来检查哪个事件发生,这意味着如果它handleRead,那么它不能同时handleWrite。而且我认为这可能会导致以下情况下的事件丢失:socket读写操作都满足EAGAIN,然后远程端同时读取和发送一些数据,因此epoll等待可能同时设置EPOLLIN和EPOLLOUT,但它只能handleRead,由于没有调用handleWrite,因此无法发送输出缓冲区中剩余的数据。
那么上面的用法错了吗?

根据 man 7 epoll QA:

  1. If more than one event occurs between epoll_wait(2) calls, are they combined or reported separately?

    They will be combined.



如果我做对了,在 epoll_wait 调用之间的单个文件描述符上可能会发生几个事件。所以我认为我应该使用多个“if if and if”来检查是否发生可读/可写/错误事件,而不是使用“if and else if”。我去看看nginx epoll模块是怎么做的,来自 https://github.com/nginx/nginx/blob/953f53921505a884f3912f2d8db5217a71c0479a/src/event/modules/ngx_epoll_module.c#L867我看到以下代码:
    if (revents & (EPOLLERR|EPOLLHUP)) {
//...
}
if ((revents & EPOLLIN) && rev->active) {
//....
rev->handler(rev);
}
if ((revents & EPOLLOUT) && wev->active) {
//....
wev->handler(wev);
}

这似乎符合我一个接一个地检查所有 EPOLLERR..​​,EPOLLIN,EPOLLOUT 事件的想法。
然后我在我的应用程序中执行与 nginx 相同的操作。但是我在实验后意识到:如果我将文件描述符添加到带有 EPOLLIN|EPOLLOUT|EPOLLET 标志的 epoll 中,并且我没有填满输出缓冲区,由于某些数据到达,我总是会在 epoll_wait 返回后设置 EPOLLOUT 标志并且这个 fd 变得可读,因此会调用冗余的 write_handler,这不是我所期望的。

我做了一些搜索,发现这种情况确实存在,并且不是由我的应用程序中的任何错误引起的。根据 epoll with edge triggered event 的最高投票答案说:

On a somewhat related note: if you register for EPOLLIN and EPOLLOUT events and assuming you never fill up the send buffer, you still get the EPOLLOUT flag set in the event returned by epoll_wait each time EPOLLIN is triggered - see https://lkml.org/lkml/2011/11/17/234 for a more detailed explanation.



这个答案中的链接说:

It's doesn't mean there's an EPOLLOUT "event", it just means a message is triggered (by the socket becoming readable) so you get a status update. In theory the program doesn't need to be told about EPOLLOUT here (it should be assuming the socket is writable already), but it doesn't do any harm.



到目前为止我对 epoll 的了解 边沿触发 模式是:
  • 当任何被监视的 fd 的状态发生变化时,epoll_wait 返回,例如从无读取 -> 可读或缓冲区已满 -> 缓冲区可以写入
  • epoll_wait 可能会为就绪列表中的每个 fd 返回一个或多个事件(标志)。
  • sturct epoll_event.events 字段中的标志指示此 fd 的当前状态。即使我们不填写输出缓冲区,由于可读,epoll_wait 返回时也会设置 EPOLLOUT 标志,因为 fd 的当前状态是可写的。

  • 如果我错了,请纠正我。
    那么我的问题是:我是否应该在每个连接中维护一个标志以指示在写入输出缓冲区时是否发生 EAGAIN,如果未设置,请不要在“if(events & EPOLLOUT)”分支中调用 write_handler/handleWrite,所以我的上层程序不会在这里被告知 EPOLLOUT 吗?

    最佳答案

    多么棒的问题(因为我有几乎相同的问题)!我将总结一下我认为我现在所知道的内容,包括您的信息性问题/描述和有用的链接,希望更聪明的人能纠正任何错误。
    是的,事件标志的 if/else 处理绝对是假的。可以肯定的是,至少两个 jar 头事件可以同时有效地到达。例如,自您上次调用 epoll_wait() 以来,读取端和写入端可能都已畅通无阻。 .当然,只要您 accept()连接,读和写突然变得可能,所以你得到一个“事件”EPOLLIN|EPOLLOUT .
    我真的不明白 epoll_wait()始终提供整个当前状态,而不仅仅是更改的状态部分 - 感谢您清除它。或许更清楚一点,epoll_wait()除非在该套接字上发生更改,否则不会返回 fd,但如果确实发生了更改,它将返回代表当前状态的所有标志。所以,我发现自己盯着 EPOLLIN|EPOLLOUT 的流。事件想知道为什么它声称有一个“输出”事件,即使我还没有写任何东西。你的回答是正确的:它只是告诉我输出端仍然是可写的。
    “我应该维护一面旗帜吗……” 是的,但我想,除了最微不足道的情况外,您可能最终会为您的读者/作者维持至少一点“我当前是否被阻止”状态.例如,如果您想以不同于数据到达方式的顺序处理数据(例如,将响应优先于请求以使您的服务器更能抵抗过载),您必须立即放弃仅让 I/到达的简单性。 O驱动一切。在写作的特殊情况下,epoll 根本没有足够的信息在“正确”的时间通知你。一旦你接受一个连接,就会有一个事件显示“你现在可以写”——但如果你是一个不可能已经收到来自客户端的请求的服务器,你可能没有什么可写的。 epoll 只是不知道你是否有东西要写,所以你总是不得不遭受本质上“无关”的事件,或者保持你自己的状态。
    除了最简单的情况外,套接字文件描述符最终无法提供足够的信息来处理 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);
    }
    在哪里 Eventable是一个 C++ 类(或其派生类),它具有决定如何处理 epoll 传递的标志所需的所有状态。 (当然,这是让内核存储指向 C++ 对象的指针,需要一个非常清楚指针所有权/生命周期的设计。)
    由于您在 Linux 上编写低级代码,您可能还关心 EPOLLRDHUP .这个不高度便携的标志可以让你保存对 read() 的调用。 .如果客户端(curl 似乎很擅长引发这种行为)关闭连接的写入端(发送 FIN),您通常会发现当 epoll 告诉您 EPOLLIN , 但是 read()返回零字节。但是,Linux 会保留一个额外的位来指示客户端的写入端(您的读取端)已关闭。所以,如果你告诉 epoll 你想要 EPOLLRDHUP事件您可以使用它来避免执行 read()他们的唯一目的是告诉你作者关闭了他们的身边。
    请注意 EPOLLIN仍然会在 EPOLLRDHUP 时打开是,AFAIK。即使你做了 shutdown(fd, SHUT_RD) .另一个例子是你通常会被驱使保持你自己对连接状态的想法。你更关心那些好心做半关机的客户 if you are implementing HTTP .

    关于c++ - epoll_wait 即使带有 EPOLLET 标志也返回 EPOLLOUT,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59288735/

    30 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com