0

我正在使用boost::asio同步 TCP 套接字连接到在同一台计算机上运行的 node.js TCP 服务器应用程序。我在 Windows 上使用 Embarcadero RAD studio XE4 构建使用 Embarcadero 集成升压版本 1.50 的 64 位应用程序。

除非 node.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(),当那里没有数据时,我不会在套接字上执行 a ,但在这种情况下我会这样做,因为所有文档都让我相信只有在对套接字执行读取或写入时才会检测到套接字断开连接。问题是,当 node.js 应用程序关闭时,上面的代码根本不会检测到断开连接。如果我正在写作,我会检测到它(代码的写入部分使用与读取相同的 boost::asio::error` 错误进行检测)但在读取时不会。

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

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

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

4

1 回答 1

5

一般来说,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;
    }
  }
}

以下输出是通过将示例程序连接到编写“测试”、“更多测试”的服务器,然后关闭连接产生的:

no data available
Read: 4 -- test -- Success
no data available
Read: 12 -- more testing -- Success
Read: 0 -- End of file
于 2014-04-01T20:52:15.397 回答