sockets - epoll_wait() 接收套接字关闭两次(read()/recv() 返回 0)

标签 sockets recv epoll

我们有一个使用 epoll 来监听和处理 http 连接的应用程序。有时 epoll_wait() 会连续两次收到 fd 上的 close 事件。含义:epoll_wait() 返回连接 fd,其中 read()/recv() 返回 0。这是一个问题,因为我将 malloc:ed 指针保存在 epoll_event 结构(struct epoll_event.data.ptr)中,并且在 fd 时释放该指针(套接字)第一次被检测为关闭。第二次就崩溃了。

这个问题在实际使用中很少出现(除了一个站点,该站点实际上每台服务器大约有 500-1000 个用户)。我可以使用每秒超过 1000 个并发连接的 http siege 来复制该问题。在这种情况下,应用程序段错误(由于无效指针)非常随机,有时在几秒钟后,通常在几十分钟后。我已经能够以每秒更少的连接来复制该问题,但为此我必须运行应用程序很长时间、很多天,甚至几周。

所有新的accept()连接fd:s都被设置为非阻塞,并作为一次性、边缘触发并等待read()可用而添加到epoll中。那么为什么当服务器负载很高时,epoll 认为我的应用程序没有收到关闭事件并将新的事件放入队列?

epoll_wait() 在它自己的线程中运行,并将 fd 事件排队以在其他地方处理。我注意到有多个关闭传入,简单的代码检查是否有事件从 epoll 到同一个 fd 连续两次发生。它确实发生了,并且两者都关闭的事件(recv(..,MSG_PEEK)告诉我这一点:))。

epoll fd 已创建:

epoll_create(1024);

epoll_wait() 运行如下:

epoll_wait(epoll_fd, events, 256, 300);

accept()之后新的fd被设置为非阻塞:

int flags = fcntl(fd, F_GETFL, 0);
err = fcntl(fd, F_SETFL, flags | O_NONBLOCK);

新的fd被添加到epoll中(客户端是malloc:ed结构指针):

static struct epoll_event ev;
ev.events = EPOLLIN | EPOLLONESHOT | EPOLLET;
ev.data.ptr = client;
err = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client->fd, &ev);

并且在接收和处理来自fd的数据后,它被重新武装(当然是从EPOLLONESHOT开始)。起初我没有使用边缘触发和非阻塞 io,但我对其进行了测试并使用它们获得了很好的性能提升。不过,这个问题在添加它们之前就存在。顺便提一句。 shutdown(fd, SHUT_RDWR) 用于其他线程,当服务器由于某些 http 错误等而需要关闭 fd 时,触发通过 epoll 接收的正确关闭事件(我实际上不知道这是否是正确的方法)这样做,但效果非常好)。

最佳答案

一旦第一个 read() 返回 0,这意味着连接已被对等方关闭。为什么内核会在这种情况下生成 EPOLLIN 事件?好吧,当您只订阅 EPOLLIN 时,没有其他方法可以指示套接字关闭。您可以添加 EPOLLRDHUP,这与检查 read() 返回 0 基本相同。但是,请确保在测试 EPOLLIN 之前测试此标志。

  if (flag & EPOLLRDHUP) {
     /* Connection was closed. */
     deleteConnectionData(...);
     close(fd); /* Will unregister yourself from epoll. */
     return;
  }

  if (flag & EPOLLIN) {
    readData(...);
  }

  if (flag & EPOLLOUT) {
    writeData(...);
  }

我对这些 block 的排序方式是相关的,并且 EPOLLRDHUP 的返回也很重要,因为 deleteConnectionData() 可能已经破坏了内部结构。由于 EPOLLIN 也是在关闭的情况下设置的,这可能会导致一些问题。忽略 EPOLLIN 是安全的,因为它无论如何都不会产生任何数据。与 EPOLLOUT 相同,因为它从不与 EPOLLRDHUP 一起发送!

关于sockets - epoll_wait() 接收套接字关闭两次(read()/recv() 返回 0),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4724137/

相关文章:

javascript - 从缓冲区中检索 FLOAT、DOUBLE、INT 或 BOOL 值

c - 读取 TCP 套接字中的 buff

perl - socket->recv() 与 <>?

c++ - TCP 服务器/客户端 : client recv() returns blank buffer

python - python socket.io客户端无法接收广播消息

c - 为什么服务器在客户端应用程序处于 STOPPED 状态后等待客户端?

c - 为什么EPOLLOUT没有被触发?

linux - epoll会通知监听同一个fd的所有进程吗?

nonblocking - "special"epoll 标志如何对应于 kqueue 标志?

c++ - 如何检测 boost tcp 套接字何时断开连接