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

时间:2011-01-18 12:38:16

标签: sockets recv epoll

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

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

所有新的accept()连接fd:s都设置为非阻塞,并添加到epoll作为单次触发,边缘触发并等待read()可用。那么为什么当服务器负载很高时,epoll认为我的应用程序没有得到close事件并排队新事件?

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

创建了epoll fd:

epoll_create(1024);

epoll_wait()运行如下:

epoll_wait(epoll_fd, events, 256, 300);

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

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

将新的fd添加到epoll(客户端是malloc:ed struct pointer):

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)用于其他线程,当服务器需要关闭fd时,因为一些http-error等而触发了epoll接收正确的关闭事件(我实际上并不知道这是否正确做到了,但它完美地运作了。)

5 个答案:

答案 0 :(得分:3)

只要第一个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(...);
  }

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

答案 1 :(得分:1)

  

epoll_wait()在它自己的线程中运行,并将fd事件排队到其他地方处理。   ...那么为什么当服务器负载很高时,epoll认为我的应用程序没有得到关闭事件并排队新事件?

假设EPOLLONESHOT没有错误(虽然我没有搜索相关的错误),你在另一个线程中处理你的epoll事件并且偶尔崩溃或在高负载下崩溃的事实可能意味着存在竞争条件在您的应用程序的某个地方。

当服务器主动关闭客户端连接时epoll事件在另一个线程中取消注册之前,epoll_event.data.ptr指向的对象可能会过早释放。

我的第一次尝试是在valgrind下运行它,看它是否报告任何错误。

答案 2 :(得分:0)

我会根据epoll(7)的以下部分重新检查自己:

Q6
关闭文件描述符会导致它自动从所有epoll集中删除吗?

o如果使用事件缓存...

那里有一些好处。

答案 3 :(得分:0)

删除EPOLLONESHOT后,在几次其他更改后问题就消失了。不幸的是,我不完全确定是什么造成的。使用带有线程的EPOLLONESHOT并再次手动将fd添加到epoll队列中肯定是个问题。 epoll结构中的数据指针也会在延迟后释放。现在工作得很好。

答案 4 :(得分:0)

注册信号0x2000以进行远程主机关闭连接 ex ev.events = EPOLLIN | EPOLLONESHOT | EPOLLET |为0x2000 并检查远程主机关闭连接是否(标志和0x2000)