Boost ASIO streambuf

The nomenclature for boost::asio::streambuf is similar to that of which is defined in the C++ standard, and used across various classes in the standard template library, wherein data is written to an output stream and data is read from an input stream. For example, one could use std::cout.put() to write to the output stream, and std::cin.get() to read from the input stream.

When manually controlling the streambuf input and output sequences, the general lifecycle of data is as follows:

  • Buffers get allocated with prepare() for the output sequence.
  • After data has been written into the output sequence’s buffers, the data will be commit()ed. This committed data is removed from the output sequence and appended to the input sequence from which it can be read.
  • Data is read from the input sequence’s buffers obtained via data().
  • Once data has been read, it can then be removed from the input sequence by consume().

When using Boost.Asio operations that operate on streambuf or stream objects that use a streambuf, such as std::ostream, the underlying input and output sequences will be properly managed. If a buffer is provided to an operation instead, such as passing passing prepare() to a read operation or data() to a write operation, then one must explicitly handle the commit() and consume().

Here is an annotated version of the example code which writes directly from an streambuf to a socket:

// The input and output sequence are empty.
boost::asio::streambuf b;
std::ostream os(&b);

// prepare() and write to the output sequence, then commit the written
// data to the input sequence.  The output sequence is empty and
// input sequence contains "Hello, World!\n".
os << "Hello, World!\n";

// Read from the input sequence, writing to the socket.  The input and
// output sequences remain unchanged.
size_t n = sock.send(b.data());

// Remove 'n' bytes from the input sequence. If the send operation sent
// the entire buffer, then the input sequence would be empty.
b.consume(n);

And here is the annotated example for reading from a socket directly into an streambuf. The annotations assume that the word “hello” has been received, but not yet read, on the socket:

boost::asio::streambuf b;

// prepare() 512 bytes for the output sequence.  The input sequence
// is empty.
auto bufs = b.prepare(512);

// Read from the socket, writing into the output sequence.  The
// input sequence is empty and the output sequence contains "hello".
size_t n = sock.receive(bufs);

// Remove 'n' (5) bytes from output sequence appending them to the
// input sequence.  The input sequence contains "hello" and the
// output sequence has 507 bytes.
b.commit(n);

// The input and output sequence remain unchanged.
std::istream is(&b);
std::string s;

// Read from the input sequence and consume the read data.  The string
// 's' contains "hello".  The input sequence is empty, the output
// sequence remains unchanged.
is >> s;

Note how in the above examples, the steam objects handled committed and consuming the streambuf’s output and input sequences. However, when the buffers themselves were used (i.e. data() and prepare()), the code needed to explicitly handle commits and consumes.

Leave a Comment