答案 0 :(得分:31)
面对严酷的网络,TCP意味着强大;即使TCP提供了看似持久的端到端连接,但这只是一个谎言,每个数据包实际上只是一个独特的,不可靠的数据报。
连接实际上只是在连接的每一端跟踪一个小状态创建的虚拟管道(源和目标端口和地址,以及本地套接字)。网络堆栈使用此状态来了解将每个传入数据包分配给哪个进程以及将每个传出数据包的标头放入哪个状态。
由于网络的基础 - 固有的无连接和不可靠 - 性质,当远程端发送FIN数据包以关闭连接时,或者如果它没有收到ACK响应,则堆栈将仅报告断开的连接发送的数据包(在超时和几次重试之后)。
由于asio的异步性质,通知正常断开连接的最简单方法是拥有一个未完成的async_read
,它将在连接关闭时立即返回error::eof
。但仅此一点仍然存在其他问题,如半开放连接和网络问题未被发现的可能性。
解决意外连接中断的最有效方法是使用某种keep-alive或ping。偶尔通过连接传输数据的尝试将允许方便地检测无意切断的连接。
TCP协议实际上有一个内置的keep-alive mechanism,可以使用asio::tcp::socket::keep_alive
在asio中配置。 TCP keep-alive的优点在于它对用户模式应用程序是透明的,只有对keep-alive感兴趣的对等体才需要配置它。缺点是您需要操作系统级访问/知识来配置超时参数,遗憾的是,它们不会通过简单的套接字选项公开,并且通常具有非常大的默认超时值(在Linux上为7200秒)。
最常见的keep-alive方法可能是在应用程序层实现它,在应用程序层,应用程序有一个特殊的noop或ping消息,除了响应时做什么都没做。此方法为您提供了实施保持活动策略的最大灵活性。
答案 1 :(得分:1)
TCP承诺监视丢弃的数据包 - 根据需要重试 - 为您提供可靠的连接,以获得可靠的定义。当然,TCP无法处理服务器崩溃或您的以太网电缆掉线或发生类似情况的情况。此外,知道您的TCP连接已启动并不一定意味着将通过TCP连接的协议准备就绪(例如,您的HTTP Web服务器或您的FTP服务器可能处于某种损坏状态)。
如果您知道通过TCP发送的协议,那么该协议中可能有一种方法可以告诉您事情是否良好(对于HTTP,它将是HEAD request)
答案 2 :(得分:1)
如果您确定远程套接字没有发送任何消息(例如,因为尚未发送请求),则可以将本地套接字设置为 nonblocking 模式,然后尝试从中读取一个或多个字节。
鉴于服务器未发送任何内容,您将收到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
内核:4.15.0-36通用
升压:1.67
但是,我不知道这种行为是否取决于任何这些版本。
答案 3 :(得分:0)
您可以在套接字上发送一个虚拟字节,看它是否会返回错误。