TCP旨在面对艰苦的网络环境时具有稳健性;尽管TCP提供了看似持久的端到端连接,实际上每个数据包都只是一个唯一的不可靠数据报。
这些连接实际上只是虚拟通道,每端都会跟踪一些状态(源端口、目的地地址和端口以及本地套接字)。网络堆栈利用这些状态来确定将每个传入的数据包分配给哪个进程,并将哪种状态放入每个传出数据包的头部。
由于底层网络本质上是无连接且不可靠的,因此仅当远端发送FIN分组关闭连接时,或者如果它没有收到已发送分组的ACK响应(经过超时和几次重试)时,堆栈才会报告断开连接。
由于asio的异步特性,最简单的检测优雅断开的方法是保留一个outstanding的async_read
,当连接关闭时该方法即可立即返回error::eof
。但这仍然存在一些问题,如半打开连接和网络问题可能未被检测到。
最有效地解决意外连接中断的方法是使用某种心跳或ping。这种间歇性地尝试在连接上传输数据的方法将允许快速检测无意中断的连接。
TCP协议实际上具有内置的心跳机制,可以使用asio::tcp::socket::keep_alive
在asio中配置。TCP keep-alive的好处是它对用户模式应用程序透明,只有关心心跳的对等方需要进行配置。缺点是你需要操作系统级别的访问和知识来配置超时参数,它们通常没有暴露简单的套接字选项,并且通常具有相当大的默认超时值(Linux上为7200秒)。
保持连接最常用的方法是在应用层实现,其中应用程序具有特殊的noop或ping消息,只有在被触发时才做出响应。这种方法可以为您提供在实施保持连接策略方面最大的灵活性。
假设服务器没有发送任何内容,则会收到asio::error::would_block
或其他错误。如果是前者,则您的本地套接字尚未检测到断开连接。如果是后者,则您的套接字已关闭。
以下是示例代码:
#include <iostream>
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/asio/steady_timer.hpp>
using namespace std;
using namespace boost;
using tcp = asio::ip::tcp;
template<class Duration>
void async_sleep(asio::io_service& ios, Duration d, asio::yield_context yield)
{
auto timer = asio::steady_timer(ios);
timer.expires_from_now(d);
timer.async_wait(yield);
}
int main()
{
asio::io_service ios;
tcp::acceptor acceptor(ios, tcp::endpoint(tcp::v4(), 0));
boost::asio::spawn(ios, [&](boost::asio::yield_context yield) {
tcp::socket s(ios);
acceptor.async_accept(s, yield);
// Keep the socket from going out of scope for 5 seconds.
async_sleep(ios, chrono::seconds(5), yield);
});
boost::asio::spawn(ios, [&](boost::asio::yield_context yield) {
tcp::socket s(ios);
s.async_connect(acceptor.local_endpoint(), yield);
// This is essential to make the `read_some` function not block.
s.non_blocking(true);
while (true) {
system::error_code ec;
char c;
// Unfortunately, this only works when the buffer has non
// zero size (tested on Ubuntu 16.04).
s.read_some(asio::mutable_buffer(&c, 1), ec);
if (ec && ec != asio::error::would_block) break;
cerr << "Socket is still connected" << endl;
async_sleep(ios, chrono::seconds(1), yield);
}
cerr << "Socket is closed" << endl;
});
ios.run();
}
并且输出:
Socket is still connected
Socket is still connected
Socket is still connected
Socket is still connected
Socket is still connected
Socket is closed
测试环境:
Ubuntu: 16.04
Kernel: 4.15.0-36-generic
Boost: 1.67
然而,我不知道这种行为是否取决于上述任何版本。
你可以在套接字上发送一个虚拟字节,看看它是否会返回错误。