Buffer sequence

124

Let's see once again how did we send messages in TCP chat server:

io::async_write(socket, io::buffer(outgoing.front()), [self = shared_from_this()] (error_code error, std::size_t bytes_transferred)
{
    self->on_write(error, bytes_transferred);
});

Take a closer look at this part:

io::buffer(outgoing.front())

boost::asio::buffer is a free function which constructs a buffer view from a given parameters. Buffer view doesn't own the memory but just hold the pointer and the size of the memory block. Therefore, that memory block must stay alive as long as the buffer view is in use.

There are two types of buffer views: io::const_buffer and io::mutable_buffer. The first one is read-only view and used when you send a data. The second one allows you to modify the data it points to and it used when you receive a data.

Since in C++ we like to keep every byte under control, you can access data pointer and size of a given buffer view with its member functions:

auto view = io::buffer(...);

// void* or void const*, depending on if the view is const or mutable
auto pointer = view.data(); 

std::size_t size = view.size();

The second parameter of async_write in the example above is not just a buffer view, but a sequence of such views. It's a normal thing when you need to send some data which is scattered across different places in memory. For example, assume you're using some sort of cyclic buffer which you're filling sequentially with byte representation of messages pending to be sent, and when you reach the end of the buffer, you continue to fill it from the beginning. Assume that some message happened to be cut in two pieces: first piece is located at the end of the buffer, and the second piece is located at the beginning. If you need to pass it into some function as a single buffer view then you will have to copy this parts into some additional buffer so the whole message could be presented as a solid memory block.

A buffer sequence is used to cover such a need and avoid unnecessary copying. A buffer sequence is any iterable range of Boost.Asio buffer views. Usually std::vector is used for this. However usually a length of such sequence is know during the compile time, and the better way is to use std::array for such sequence.

Let's say we want to compose a const buffer sequence which consist of std::string, char array and a pair of pointer and size:

std::string first = "Hello Boost.Asio!";
char second[15] = {0};
char const* pointer = get_data();
std::size_t size = get_size();

std::array<io::const_buffer, 3> sequence{first, second, io::buffer(pointer, size)};

// Now we can pass this sequence into I/O function:
io::async_write(socket, sequence, handler);

Straightforward construction of such array looks a bit clumsy. We could use helper facilities for composing such sequences with C++17:

template <typename... Args>
auto const_sequence(Args&&... args)
{
    return std::array<io::const_buffer, sizeof...(Args)>{io::buffer(args)...};
}

template <typename... Args>
auto sequence(Args&&... args)
{
    return std::array<io::mutable_buffer, sizeof...(Args)>{io::buffer(args)...};
}

io::async_write(socket, const_sequence(first, second, io::buffer(pointer, size)), handler);

Ah, much better!

All I/O functions operating on data support both types of data view: a single buffer view and a buffer sequence. Also those functions hold a copy of a given buffer sequence, so you don't have to keep constructed sequence alive while asynchronous operation is in progress. And that's another reason to prefer std::array over std::vector to prevent unnecessary memory allocations.

Lesson 19
Share this page:

Learning plan

Writing a very simple client application in C++ with Boost.Asio
How to deal with completion handlers manually to combine Boost.Asio with other APIs
Let's take a break and briefly look across everything we've learned so far
20. Buffer sequence
A closer look on how to pass data views into Boost.Asio functions
How to operate on the underlying buffer sequence data with Boost.Asio free functions
How to read data from Boost.Asio dynamic buffers
How to work with Boost.Asio dynamic buffers manually