无法检测到C++同步boost :: asio :: ip :: tcp :: socket连接被关闭

mat*_*975 0 c++ sockets c++builder boost-asio c++builder-xe4

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

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

我目前的代码是在尝试了解增强文档和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;
}
Run Code Online (Sandbox Code Playgroud)

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

显然,我无法执行大于可用字节数量的读取,或者我的线程将阻塞,我将无法在我的线程循环中执行写入.

我错过了另一个特定的增强错误代码来检测错误情况吗?或者这是一个特别是零长度读取的问题.如果是这样的话,我还有其他选择吗?

目前我正在让node.js服务器在关闭时关闭我检测到然后自己关闭客户端的时候写入特定的消息.但是,这有点像黑客,如果可能的话,我更愿意采用干净的方法来检测断开连接.

Tan*_*ury 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;
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

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

no data available
Read: 4 -- test -- Success
no data available
Read: 12 -- more testing -- Success
Read: 0 -- End of file
Run Code Online (Sandbox Code Playgroud)