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 withboost::asio::error::would_block
if they are unable to perform the requested operation immediately. Iffalse
, 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