无法检测到C++同步提升::asio::ip::tcp::套接字连接正在关闭

Unable to detect C++ synchronous boost::asio::ip::tcp::socket connection being closed

本文关键字:连接 套接字 tcp ip asio C++ 检测 同步      更新时间:2023-10-16

我正在使用boost::asio与在同一台计算机上运行的节点.js TCP服务器应用程序建立同步TCP套接字连接。我在Windows上使用Embarcadero RAD studio XE4构建64位应用程序,该应用程序使用Embarcadero集成增强版本1.50。

除了节点.js TCP服务器关闭外,一切正常。发生这种情况时,我的C++客户端应用程序在从套接字读取时没有检测到断开连接。但是,当我写入套接字时,它确实会检测到断开连接。

我当前的代码是在尝试理解 BOOST 文档和 SO 上的各种答案后编写的。代码的读取部分如下(为了紧凑性,我省略了对错误代码的检查)

boost::system::error_code ec;
boost::asio::io_service io_service;
boost::asio::ip::tcp::socket s(io_service);
boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string("127.0.0.1"),m_port);
ec = s.connect(ep,ec);
std::size_t bytes_available = s.available(ec);
std::vector<unsigned char> data(bytes_available,0);
size_t read_len = boost::asio::read(s,  boost::asio::buffer(data),boost::asio::transfer_at_least(bytes_available),ec);
if ((boost::asio::error::eof == ec) || (boost::asio::error::connection_reset == ec))
{
    // disconnection
    break;
}

这不是一个很棒的系统,因为它在循环内自己的线程中运行并不断轮询数据,直到程序关闭。通常,当没有数据时,我不会对套接字执行read(),但在这种情况下我会这样做,因为所有文档都让我相信只有在对套接字执行读取或写入时才会检测到套接字断开连接。问题在于,当节点.js应用程序关闭时,上面的代码根本无法检测到断开连接。如果我在写,我会检测到它(代码的写入部分使用与读取检测相同的 boost::asio::error' 错误),但在读取时没有。

显然,我

无法对大于可用字节量执行读取,否则我的线程将阻塞,并且我以后将无法在线程循环中执行写入。

我是否缺少另一个特定的提升错误代码来检测错误条件?还是专门针对零长度读取的问题。如果是这种情况,我还有其他选择吗?

目前,我正在让节点.js服务器在关闭时向套接字写出特定消息,我正在检测该消息,然后自己关闭客户端。但是,这有点黑客,如果可能的话,我更喜欢一种干净的方法来检测断开连接。

一般来说,Boost.Asio 的 read() 函数在以下任一情况下返回:

  • 缓冲区已满。
  • 满足
  • 完整条件。
  • 发生错误。
没有

具体说明检查这些条件的顺序。 但是,上次我查看实现时,Boost.Asio 在尝试从套接字读取之前将流上读取零字节的操作视为无操作。 因此,不会观察到文件结束错误,因为 0 大小的缓冲区被视为已满。

虽然使用异步操作可能会提供更好的结果和可伸缩性,但仍可以通过同步操作以非阻塞方式检测断开连接。 默认情况下,同步操作处于阻塞状态,但可以通过 socket::non_blocking()功能。 文档指出:

如果true,套接字的同步操作将失败,如果它们无法立即执行请求的操作,则boost::asio::error::would_block。如果false,同步操作将阻塞,直到完成。

因此,如果同步操作设置为不阻塞,并且读取操作尝试读取至少 1 个字节,则可以以非阻塞方式观察到断开连接。


下面是一个完整的示例,演示了检测断开连接的非阻塞同步read()操作。 为了限制打印消息,我选择在操作被阻止时执行睡眠(即有连接但没有数据可供读取)。

#include <algorithm>
#include <iostream>
#include <vector>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
int main(int argc, char* argv[])
{
  if (argc != 2)
  {
    std::cerr << "Usage: <port>n";
    return 1;
  }
  // Create socket and connet to local port.
  namespace ip = boost::asio::ip;
  boost::asio::io_service io_service;
  ip::tcp::socket socket(io_service);
  socket.connect(ip::tcp::endpoint(
      ip::address::from_string("127.0.0.1"), std::atoi(argv[1])));
  // By setting the socket to non-blocking, synchronous operations will
  // fail with boost::asio::error::would_block if they cannot immediately
  // perform the requested operation.
  socket.non_blocking(true);
  // Synchronously read data.
  std::vector<char> data;
  boost::system::error_code ec;
  for (;;)
  {
    // Resize the buffer based on the amount of bytes available to be read.
    // Guarantee that the buffer is at least 1 byte, as Boost.Asio treats
    // zero byte read operations as no-ops.
    data.resize(std::max<std::size_t>(1, socket.available(ec)));
    // Read all available data.
    std::size_t bytes_transferred =
        boost::asio::read(socket, boost::asio::buffer(data), ec);
    // If no data is available, then continue to next iteration.
    if (bytes_transferred == 0 && ec == boost::asio::error::would_block)
    {
      std::cout << "no data available" << std::endl;
      boost::this_thread::sleep_for(boost::chrono::seconds(3));
      continue;
    }            
    std::cout << "Read: " << bytes_transferred << " -- ";
    if (bytes_transferred)
    {
        std::cout.write(&data[0], bytes_transferred);
        std::cout << " -- ";
    }
    std::cout << ec.message() << std::endl;
    // On error, such as a disconnect, exit the loop.
    if (ec && ec != boost::asio::error::would_block)
    {
      break;
    }
  }
}

通过将示例程序连接到写入"test","more testing"的服务器,然后关闭连接,产生了以下输出:

no data available
Read: 4 -- test -- Success
no data available
Read: 12 -- more testing -- Success
Read: 0 -- End of file