Dynamic buffers, part 2
In the previous lesson we've learned how to read a data from a dynamic buffer and how to consume data pieces that were successfully processed. However before you could read something from the buffer, someone else should write that something there, right?
Logically dynamic buffer separated into two sequences: input and output. Input sequence represents a data which is already obtained and ready to be read, processed and consumed. Output sequence represents a data which is being currently prepared before it go into the input sequence. We've already learned how to read a data from the input sequence with dynamic buffer's data
member function. Also we learned how to throw away (or consume) processed data with consume
member function.
In the earlier lessons dynamic buffers were filled with data somewhere inside boost::asio::async_read_until
free function. Let's see how could we do that on our own. To do that we will need two new member functions of a dynamic buffer: prepare
and commit
. Consider the following example:
boost::asio::streambuf streambuf;
// Allocate (or prepare) 1024 bytes for the data in the output sequence
boost::asio::mutable_buffer view = streambuf.prepare(1024);
// Receive a data into the allocated buffer
std::size_t bytes_transferred = socket.receive(view);
// Transfer bytes_transferred bytes from the output sequence into the input sequence
streambuf.commit(bytes_transferred);
// Now this input data is accessible with data member function
auto data = streambuf.data();
// And consumed after it is no longer needed
streambuf.consume(bytes_transferred);
So, working with functions like boost::asio::async_read_until
you just have to deal with the input sequence when the output sequence is delt with inside async_read_until
function. In the example above we deal with both sequences on our own. Let's take a closer look:
boost::asio::mutable_buffer view = streambuf.prepare(1024);
prepare
member function allocates required amount of memory in the output sequence and returns a mutable buffer view of that allocated memory block. Now, if you want to put some data that should be read from the dynamic buffer later, you should write it via this mutable buffer view. And that's exactly what we did:
std::size_t bytes_transferred = socket.receive(view);
Now, bytes_transferred
bytes were written into the input sequence. bytes_transferred
value could be less than view
size which is 1024 bytes in our case. So we've could continue filling the memory allocated further. When we decide that some amount of data is well-formed and ready to be processed by the consumer, we transfer that amount (bytes_transferred
bytes in our case) from the output to the input sequence with commit
member function:
streambuf.commit(bytes_transferred);
Note that if you commit lesser amount of bytes than was prepared before with prepare
member function, then the remaining amount of data in the prepared buffer will be discarded.
On the other hand, you can work with functions like boost::asio::async_write
with dynamic buffers. In that case you have to prepare and commit data into the output sequence on your own, while the input sequence is delt with inside async_write
function:
boost::asio::streambuf streambuf;
boost::asio::mutable_buffer view = streambuf.prepare(1024);
// Write something into the mutable buffer
// ...
streambuf.commit(1024);
boost::asio::async_write(socket, streambuf, [&] (error_code error, std::size_t bytes_transferred)
{
// No need to call streambuf.consume() here
});