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