- 不希望服务程序超载
- file descriptor 是稀缺资源, fd 耗尽是一件麻烦事
若accpet(2) 返回 EMFILE 该如何应对?这意味着本进程的文件描述符已经到达上限,无法为新连接创建 socket 文件描述符。但是,既然没有 socket 文件描述符来表示这个连接,我们就无法 close(2)它。程序继续运行,回到epoll_wait处 。这时 epoll_wait 会马上返回,因为新连接还在等待处理,listening fd 还是可读的。这样程序就陷入了 busy loop,CPU 占用率接近 100%,这既影响同一 eventloop 上的连接,也影响同一机器上的其他服务。
陈硕的 《linux多线程服务端编程》中提到了以下几种做法:
- 调高进程的文件描述符数目。治标不治本
- 死等。鸵鸟算法
- 退出程序。不可取
- 关闭 listenning fd. 那么什么时候再打开呢?
- 改用 edge trigger。如果漏掉一次 accept(2),程序再也不会接受新连接(这会出现客户端无响应的情况)
- 准备一个空闲的文件描述符。
准备一个空闲的文件描述符。遇到这种情况,先关闭这个空闲文件,获得一个文件描述符地名额;再accept(2)拿到新socket 连接的描述符;随后立刻close(2)它,这样就优雅地断开了客户端连接;最后重新打开一个空闲文件,把"坑"占住,以备再次出现这种情况时使用 - 自己设置一个低一点的soft limit.
muduo网络库中使用的是,第6种,代码如下:
void Acceptor::handleRead()
{
loop_->assertInLoopThread();
InetAddress peerAddr;
//FIXME loop until no more
int connfd = acceptSocket_.accept(&peerAddr);
if (connfd >= 0)
{
// string hostport = peerAddr.toIpPort();
// LOG_TRACE << "Accepts of " << hostport;
if (newConnectionCallback_)
{
newConnectionCallback_(connfd, peerAddr);
}
else
{
sockets::close(connfd);
}
}
else
{
LOG_SYSERR << "in Acceptor::handleRead";
// Read the section named "The special problem of
// accept()ing when you can't" in libev's doc.
// By Marc Lehmann, author of libev.
if (errno == EMFILE)
{
// 描述符用尽
::close(idleFd_);
idleFd_ = ::accept(acceptSocket_.fd(), NULL, NULL);
::close(idleFd_);
// 再次打开占位
idleFd_ = ::open("/dev/null", O_RDONLY | O_CLOEXEC);
}
}
}