Boost:ASIO 接受器在 hours/days 之后以 CLOSE_WAIT 状态堆叠套接字

Boost:ASIO acceptor stacks sockets in CLOSE_WAIT state after hours/days

我有一个服务器应用程序为数千个客户端提供基本的 TCP 操作服务。它接受纯文本和 TLS 连接。

一些最受请求的服务器几个月以来都面临着一个奇怪的问题。 几个小时或几天后,有许多套接字阻塞在 CLOSE_WAIT 状态,直到服务器由于缺少文件描述符而停止服务请求。

此处的套接字图表: https://ibb.co/WsvS1D9

我们尝试添加日志记录,以便我们可以看到套接字在第一次读取操作(对于纯文本套接字)或握手(对于 TLS)套接字时被阻塞。 发生这种情况时,async_read_untilasync_handshake 函数永远不会完成,并且永远不会调用关联的 deadline_timer 回调。好像async方法在阻塞线程执行!

连接从接受器开始

Log::writeLine("("+ StringUtils::printPointer(static_cast<void *>(this)) + ") [FD:"+
                       StringUtils::toString(socket().native_handle())+"] "+" Connection opened", Log::LEVEL_INFO);

/// Set a deadline for the read operation.
inout_deadline_.expires_from_now(boost::posix_time::seconds(TIMEOUT));

/// initiate calls to timer check
inout_deadline_.async_wait(
        strand_.wrap(boost::bind(&TcpServerConnection::check_deadline,
                                 shared_from_this(), boost::asio::placeholders::error, &inout_deadline_)));

Log::writeLine("("+ StringUtils::printPointer(static_cast<void *>(this)) + ") [FD:"+
               StringUtils::toString(socket().native_handle())+"] "+" async_wait initialized", Log::LEVEL_INFO);

if (useTLS_) {
    /// Start TLS handshake
    Log::writeLine("("+ StringUtils::printPointer(static_cast<void *>(this)) + ") [FD:"+
                   StringUtils::toString(socket().native_handle())+"] "+" async_handshake initialized", Log::LEVEL_INFO);

    socket_.async_handshake(boost::asio::ssl::stream_base::server,
                            strand_.wrap(
                                    boost::bind(&TcpServerConnection::handle_handshake, shared_from_this(),
                                                boost::asio::placeholders::error)));
} else {
    startReadLine();
}

在 startReadLine() 中:

inout_deadline_.expires_from_now(boost::posix_time::seconds(TIMEOUT));

Log::writeLine("("+ StringUtils::printPointer(static_cast<void *>(this)) + ") [FD:"+
               StringUtils::toString(socket().native_handle())+"] "+" async_read_until initialized", Log::LEVEL_INFO);
if(useTLS_) {
    boost::asio::async_read_until(socket_, lineBuffer_, lineDelimiter_,
      strand_.wrap(
      boost::bind(&TcpServerConnection::handle_read_line, shared_from_this(),
              boost::asio::placeholders::error,
              boost::asio::placeholders::bytes_transferred,
              false)));
} else {
    boost::asio::async_read_until(socket_.next_layer(), lineBuffer_, lineDelimiter_,
      strand_.wrap(
      boost::bind(&TcpServerConnection::handle_read_line, shared_from_this(),
              boost::asio::placeholders::error,
              boost::asio::placeholders::bytes_transferred,
              false)));
}

下面是我们为处于 CLOSE_WAIT 状态的套接字获得的 2 个日志示例:

普通插座

Jan  4 18:30:28 INFO: (0x7efcf84865b0) [FD:831] Incoming connection: SOCKET RBUF=262142 SBUF=262142
Jan  4 18:30:28 INFO: (0x7efcf84865b0) [FD:831]  Connection opened
Jan  4 18:30:28 INFO: (0x7efcf84865b0) [FD:831]  async_wait initialized
Jan  4 18:30:28 INFO: (0x7efcf84865b0) [FD:831]  startReadLine() called!
Jan  4 18:30:28 INFO: (0x7efcf84865b0) [FD:831]  async_read_until initialized

handle_read_line()check_deadline() 永远不会被调用

TLS 套接字

Jan  4 17:33:06 INFO: (0x7efcfc14a7f0) [FD:513] Incoming connection: SOCKET RBUF=262142 SBUF=262142
Jan  4 17:33:06 INFO: (0x7efcfc14a7f0) [FD:513]  Connection opened
Jan  4 17:33:06 INFO: (0x7efcfc14a7f0) [FD:513]  async_wait initialized
Jan  4 17:33:06 INFO: (0x7efcfc14a7f0) [FD:513]  async_handshake initialized

handle_handshake()check_deadline() 永远不会被调用

我们尝试更新到最新版本的 Boost (1.75) 和 OpenSSL (1.1.1i),但没有任何变化。

所有帮助将不胜感激:)

我终于偶然找到了解决方法!

我设置了一个任务,每小时检查 CLOSE_WAIT 个套接字,如果发现比平常多,则重新启动 ioservice。

令人惊讶的是,运行 ss 命令在 CLOSE_WAIT 状态上升期间(以及在达到文件描述符限制之前)的简单事实使得所有处于 CLOSE_WAIT 状态的套接字切换到LAST_ACK然后几秒后松手...

最终,文件描述符限制从未达到,ioservice 也没有重新启动!!!

确切的命令是(运行 应用进程每小时执行一次): ss -n -p state close-wait | wc -l

具有解决方法的服务器图: https://ibb.co/qBTrykn