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_until
和 async_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
我有一个服务器应用程序为数千个客户端提供基本的 TCP 操作服务。它接受纯文本和 TLS 连接。
一些最受请求的服务器几个月以来都面临着一个奇怪的问题。
几个小时或几天后,有许多套接字阻塞在 CLOSE_WAIT
状态,直到服务器由于缺少文件描述符而停止服务请求。
此处的套接字图表: https://ibb.co/WsvS1D9
我们尝试添加日志记录,以便我们可以看到套接字在第一次读取操作(对于纯文本套接字)或握手(对于 TLS)套接字时被阻塞。
发生这种情况时,async_read_until
和 async_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