我们有一个使用 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都被设置为非阻塞,并以一次性、边缘触发的方式添加到epoll中,并等待read()可用。那么为什么当服务器负载很高时,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 接收的正确关闭事件(我实际上不知道这是否是正确的方法)这样做,但效果很好)。