我们有一个使用 epoll 来监听和处理 http-connections 的应用程序。有时 epoll_wait() 在“行”中两次接收 fd 上的关闭事件。含义:epoll_wait() 返回连接 fd,其中 read()/recv() 返回 0。这是一个问题,因为我将 malloc:ed 指针保存在 epoll_event 结构 (struct epoll_event.data.ptr) 中,并且在 fd 时释放(socket) 第一次被检测为关闭。第二次崩溃了。
这个问题在实际使用中很少发生(除了一个站点,实际上每台服务器大约有 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, 事件, 256, 300);
在 accept() 之后将 new fd 设置为非阻塞:
int flags = fcntl(fd, F_GETFL, 0); 错误 = fcntl(fd, F_SETFL, 标志 | O_NONBLOCK);
新的 fd 被添加到 epoll(客户端是 malloc:ed struct 指针):
静态结构 epoll_event ev; ev.events = EPOLLIN | EPOLLONESHOT | 埃波莱特; ev.data.ptr = 客户端; err = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client->fd, &ev);
并且在接收和处理来自 fd 的数据后,它会重新武装(当然是从 EPOLLONESHOT 开始)。起初我没有使用边缘触发和非阻塞 io,但我对其进行了测试并使用它们获得了不错的性能提升。这个问题在添加它们之前就存在了。顺便提一句。shutdown(fd, SHUT_RDWR) 在其他线程上用于触发适当的关闭事件,以便在服务器由于某些 http 错误等需要关闭 fd 时通过 epoll 接收(我实际上不知道这是否是正确的方法这样做,但效果很好)。