Pseudo-code
boost::asio::streambuf my_buffer;
boost::asio::ip::tcp::socket my_socket;
auto read_handler = [this](const boost::system::error_code& ec, size_t bytes_transferred) {
// my logic
};
my_socket.async_receive(my_buffer.prepare(512),
read_handler);
When using traditional recv
with non-blocking socket, it returns -1 when there is nothing to read from socket.
But use of async_receive
does not call read_handler
if there is no data, and it waits infinitely.
How to realize such a logic (asynchronously) that calls read_handler
with bytes_transferred == 0
(possibly with error code set) when there is nothing to read from socket?
(async_read_some
has the same behavior).
In short, immediately after initiating the async_receive()
operation, cancel it. If the completion handler is invoked with boost::asio::error::operation_aborted
as the error, then the operation blocked. Otherwise, the read operation completed with success and has read from the socket or failed for other reasons, such as the remote peer closing the connection.
socket.async_receive(boost::asio::buffer(buffer), handler);
socket.cancel();
Within the initiating function of an asynchronous operation, a non-blocking read will attempt to be made. This behavior is subtlety noted in the async_receive()
documentation:
Regardless of whether the asynchronous operation completes immediately or not, [...]
Hence, if the operation completes immediately with success or error, then the completion handler will be ready for invocation and is not cancelable. On the other hand, if the operation would block, then it will be enqueued into the reactor for monitoring, where it becomes cancelable.
One can also obtain similar behavior with synchronous operations by enabling non-blocking mode on the socket. When the socket is set to non-blocking, synchronous operations that would block will instead fail with boost::asio::error::would_block
.
socket.non_blocking(true);
auto bytes_transferred = socket.receive(
boost::asio::buffer(buffer), 0 /* flags */, error);
Here is a complete example demonstrating these behaviors:
#include <array>
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
// This example is not interested in the handlers, so provide a noop function
// that will be passed to bind to meet the handler concept requirements.
void noop() {}
void print_status(
const boost::system::error_code& error,
std::size_t bytes_transferred)
{
std::cout << "error = (" << error << ") " << error.message() << "; "
"bytes_transferred = " << bytes_transferred
<< std::endl;
}
int main()
{
using boost::asio::ip::tcp;
// Create all I/O objects.
boost::asio::io_service io_service;
tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), 0));
tcp::socket socket1(io_service);
tcp::socket socket2(io_service);
// Connect the sockets.
acceptor.async_accept(socket1, boost::bind(&noop));
socket2.async_connect(acceptor.local_endpoint(), boost::bind(&noop));
io_service.run();
io_service.reset();
std::array<char, 512> buffer;
// Scenario: async_receive when socket has no data.
// Within the intiating asynchronous read function, an attempt to read
// data will be made. If it fails, it will be added to the reactor,
// for monitoring where it can be cancelled.
{
std::cout << "Scenario: async_receive when socket has no data"
<< std::endl;
socket1.async_receive(boost::asio::buffer(buffer), &print_status);
socket1.cancel();
io_service.run();
io_service.reset();
}
// Scenario: async_receive when socket has data.
// The operation will complete within the initiating function, and is
// not available for cancellation.
{
std::cout << "Scenario: async_receive when socket has data" << std::endl;
boost::asio::write(socket2, boost::asio::buffer("hello"));
socket1.async_receive(boost::asio::buffer(buffer), &print_status);
socket1.cancel();
io_service.run();
}
// One can also get the same behavior with synchronous operations by
// enabling non_blocking mode.
boost::system::error_code error;
std::size_t bytes_transferred = 0;
socket1.non_blocking(true);
// Scenario: non-blocking synchronous read when socket has no data.
{
std::cout << "Scenario: non-blocking synchronous read when socket"
" has no data." << std::endl;
bytes_transferred = socket1.receive(
boost::asio::buffer(buffer), 0 /* flags */, error);
assert(error == boost::asio::error::would_block);
print_status(error, bytes_transferred);
}
// Scenario: non-blocking synchronous read when socket has data.
{
std::cout << "Scenario: non-blocking synchronous read when socket"
" has data." << std::endl;
boost::asio::write(socket2, boost::asio::buffer("hello"));
bytes_transferred = socket1.receive(
boost::asio::buffer(buffer), 0 /* flags */, error);
print_status(error, bytes_transferred);
}
}
Output:
Scenario: async_receive when socket has no data
error = (system:125) Operation canceled; bytes_transferred = 0
Scenario: async_receive when socket has data
error = (system:0) Success; bytes_transferred = 6
Scenario: non-blocking synchronous read when socket has no data.
error = (system:11) Resource temporarily unavailable; bytes_transferred = 0
Scenario: non-blocking synchronous read when socket has no data.
error = (system:0) Success; bytes_transferred = 6