boost::asio::ip::tcp::socket是否已连接?

19

在执行读写操作之前,我希望验证连接状态。

有办法创建一个isConnect()方法吗?

我看到了这个链接,但它看起来很“丑陋”。

我也测试过is_open()函数,但它的行为与期望不符。


@joshperry 确实不是同一个问题。 - John
4个回答

33

TCP旨在面对艰苦的网络环境时具有稳健性;尽管TCP提供了看似持久的端到端连接,实际上每个数据包都只是一个唯一的不可靠数据报。

这些连接实际上只是虚拟通道,每端都会跟踪一些状态(源端口、目的地地址和端口以及本地套接字)。网络堆栈利用这些状态来确定将每个传入的数据包分配给哪个进程,并将哪种状态放入每个传出数据包的头部。

Virtual TCP Conduit

由于底层网络本质上是无连接且不可靠的,因此仅当远端发送FIN分组关闭连接时,或者如果它没有收到已发送分组的ACK响应(经过超时和几次重试)时,堆栈才会报告断开连接。

由于asio的异步特性,最简单的检测优雅断开的方法是保留一个outstanding的async_read,当连接关闭时该方法即可立即返回error::eof。但这仍然存在一些问题,如半打开连接和网络问题可能未被检测到。

最有效地解决意外连接中断的方法是使用某种心跳或ping。这种间歇性地尝试在连接上传输数据的方法将允许快速检测无意中断的连接。

TCP协议实际上具有内置的心跳机制,可以使用asio::tcp::socket::keep_alive在asio中配置。TCP keep-alive的好处是它对用户模式应用程序透明,只有关心心跳的对等方需要进行配置。缺点是你需要操作系统级别的访问和知识来配置超时参数,它们通常没有暴露简单的套接字选项,并且通常具有相当大的默认超时值(Linux上为7200秒)。

保持连接最常用的方法是在应用层实现,其中应用程序具有特殊的noop或ping消息,只有在被触发时才做出响应。这种方法可以为您提供在实施保持连接策略方面最大的灵活性。


使用保持连接的方法有一个问题:我无法访问应用程序运行的服务器,因此我无法更改保持连接超时时间(这里是7200秒)。但我喜欢这个响应,因此如果需要再次使用,我将启用TCP保持连接。谢谢。 - coelhudo
我忘了说我做了什么,我只是尝试读/写,检查错误并根据这个错误做出决定。 - coelhudo
图片不再显示了,你能修复一下吗?我想看看插图。谢谢。 - coelhudo
@joshperry 默认的超时时间值是否需要在客户端和服务器端都进行修改? - John

1
TCP承诺会检测丢包情况,并在适当时重试,以提供可靠的连接,但这里的“可靠”定义有所不同。当然,TCP无法处理服务器崩溃、以太网电缆断开或类似情况。此外,知道TCP连接正常工作并不意味着要通过TCP连接传输的协议已经准备好(例如,您的HTTP Web服务器或FTP服务器可能处于某种破损状态)。
如果您知道通过TCP发送的协议,则该协议可能有一种方法告诉您是否处于良好状态(对于HTTP,它将是一个HEAD请求)。

0
如果您确定远程套接字没有发送任何内容(例如,因为您尚未向其发送请求),那么可以将本地套接字设置为非阻塞模式,并尝试从中读取一个或多个字节。

假设服务器没有发送任何内容,则会收到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

然而,我不知道这种行为是否取决于上述任何版本。


-2

你可以在套接字上发送一个虚拟字节,看看它是否会返回错误。


3
如果您连接上了,当另一端收到这个虚拟字节时会发生什么?这不是一个好的解决方案。 - jmucchiello
2
通常这种应用程序会定期发送心跳信号。此外,在Windows(win32)上没有一种可靠地检查连接的方法,而boost :: asio :: ip :: tcp :: socket将在其之上实现。@jmucchiello--如果您曾经在分布式系统上工作过,您就会知道连接始终通过心跳进行检查,通常是一个字节。希望这可以帮助OP。 - vehomzzz
有一个问题,我不知道服务器端的行为。我找到的解决方案是在操作之前调用connect并检查错误条件。 - coelhudo
1
你需要了解服务器的协议(查看文档或询问供应商);尝试连接可能非常昂贵。 - vehomzzz
安德烈:他的套接字可能是HTTP/1.1 Keep Alive连接的客户端。该协议是否有“发送一个字节”的心跳?我已经从事分布式系统工作20多年了,并不是所有的系统都有心跳。其中一些,比如HTTP/1.1,使用“错误重连”方法。你的答案说“发送一个虚拟字节”,但没有包括你评论中发现的任何警告。这不是正确的答案。你的评论说“你需要知道协议”。请将此作为你的实际答案,因为当前的答案存在缺陷。 - jmucchiello
在Windows(win32)上没有一种可靠地检查连接的方法?有参考资料吗? - John

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接