Question

I am using boost::asio to make a synchronous TCP socket connection to a node.js TCP server application that runs on the same computer. I am using Embarcadero RAD studio XE4 on Windows building 64 bit application that uses the Embarcadero integrated boost version 1.50.

Things work fine except when the node.js TCP server shuts down. When this occurs my C++ client application does not detect the disconnection when I read from the socket. However, it does detect the disconnection when I write to the socket.

My current code was written after trying to make sense of the boost documentation and a variety of answers here on SO. The reading portion of the code is as follows (I have omitted the checking of the error code for compactness)

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;
}

This is not a great system as it is running in its own thread inside a loop and polling for data constantly until the program is shutdown. Normally I would not perform a read() on a socket when there is no data there but I do it in this case since all documentation leads me to believe that the socket disconnection is detected only when performing a read or write to the socket. The problem is that the above code is simply never detecting the disconnection when the node.js application shuts down. If I am writing I detect it (the write part of the code is using the same boost::asio::error` errors as the read for detection) but not when reading.

Obviously I cannot perform a read for byte amounts larger than that are available or my thread will block and I will be unable to perform writes later on in my thread loop.

Am I missing another specific boost error code to detect the error condition? Or is it a problem specifically with the zero length read. If that is the case are there any other options available to me?

Currently I am getting the node.js server to write out a specific message when it to the socket when it shuts down which I am detecting and then closing the client end myself. However, this is a bit of a hack and I would prefer a clean way to detect the disconnect if possible.

Was it helpful?

Solution

In general, Boost.Asio's read() functions return when either:

  • The buffer is full.
  • The complete condition has been satisfied.
  • An error has occurred.

It is unspecified as to the order in which these conditions are checked. However, last time I looked at the implementation, Boost.Asio treated operations that read zero bytes on a stream as a no-op prior to attempting reading from the socket. Hence, an end of file error will not be observed, because the 0 size buffer is considered full.

While using asynchronous operations will likely provide better results and scalability, detecting a disconnect in a non-blocking manner can still be accomplished with synchronous operations. By default, the synchronous operations are blocking, but this behavior can be controlled via the socket::non_blocking() function. The documentation states:

If true, the socket's synchronous operations will fail with boost::asio::error::would_block if they are unable to perform the requested operation immediately. If false, synchronous operations will block until complete.

Therefore, if the synchronous operations are set to not block and the read operation attempts to read a minimum of 1 byte, then a disconnect can be observed in a non-blocking manner.


Here is a complete example demonstrating a non-blocking synchronous read() operation that detects a disconnect. In order to throttle the print messages, I have opted to perform a sleep when the operation would have blocked (i.e. has a connection but no data is available to 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;
    }
  }
}

The following output was produced by connecting the example program to a server that wrote "test", "more testing", then closed the connection:

no data available
Read: 4 -- test -- Success
no data available
Read: 12 -- more testing -- Success
Read: 0 -- End of file
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top