Buffer Sequences

Corosio I/O operations work with buffer sequences from Boost.Capy. This page explains how to use buffers effectively.

Code snippets assume:

#include <boost/corosio.hpp>
#include <boost/capy/buffers.hpp>
#include <boost/capy/read.hpp>
#include <boost/capy/write.hpp>
#include <boost/capy/task.hpp>

namespace corosio = boost::corosio;
namespace capy    = boost::capy;

Buffer Types

mutable_buffer

A writable region of memory:

char data[1024];
capy::mutable_buffer buf(data, sizeof(data));

const_buffer

A read-only region of memory:

std::string msg = "Hello";
capy::const_buffer buf(msg.data(), msg.size());

Creating Buffers

From Raw Arrays

char data[1024];
capy::mutable_buffer mbuf(data, sizeof(data));

const char* str = "Hello";
capy::const_buffer cbuf(str, 5);

From std::string

std::string s = "Hello, World!";

// Writable (be careful with string invalidation)
capy::mutable_buffer mbuf(s.data(), s.size());

// Read-only
capy::const_buffer cbuf(s.data(), s.size());

From std::vector

std::vector<char> vec(1024);
capy::mutable_buffer buf(vec.data(), vec.size());

Buffer Sequences

I/O operations accept sequences of buffers for scatter/gather I/O:

Single Buffer

A single buffer is a valid buffer sequence:

capy::mutable_buffer buf(data, size);
auto [ec, n] = co_await sock.read_some(buf); // Works directly

Multiple Buffers

Use arrays or vectors of buffers:

// Array of buffers
std::array<capy::mutable_buffer, 2> bufs = {
    capy::mutable_buffer(header, header_size),
    capy::mutable_buffer(body, body_size)};
auto [ec, n] = co_await sock.read_some(bufs);

// Vector of buffers
std::vector<capy::const_buffer> send_bufs;
send_bufs.push_back(capy::const_buffer(header.data(), header.size()));
send_bufs.push_back(capy::const_buffer(body.data(), body.size()));
auto [ec, n] = co_await sock.write_some(send_bufs);

Buffer Sequence Concepts

Corosio uses concepts from Capy:

// Readable buffers (for writing to sockets)
template<capy::ConstBufferSequence ConstBufferSequence>
auto write_some(ConstBufferSequence const& buffers);

// Writable buffers (for reading from sockets)
template<capy::MutableBufferSequence MutableBufferSequence>
auto read_some(MutableBufferSequence const& buffers);

A type satisfies these concepts if it’s iterable and yields buffer types.

Buffer Size

Get the total size of a buffer sequence:

char header[16];
char body[1024];

std::array<capy::mutable_buffer, 2> bufs = {
    capy::mutable_buffer(header, sizeof(header)),
    capy::mutable_buffer(body, sizeof(body))};
std::size_t total = capy::buffer_size(bufs); // 1040

Memory Safety

Lifetime

Buffers don’t own memory. The underlying storage must outlive the I/O operation:

// WRONG: buffer outlives string
capy::task<void>
bad_example(corosio::tcp_socket& sock)
{
    capy::const_buffer buf;
    {
        std::string temp = "Hello";
        buf              = capy::const_buffer(temp.data(), temp.size());
    } // temp destroyed here!

    std::ignore = co_await sock.write_some(buf); // Undefined behavior
}

// CORRECT: keep storage alive
capy::task<void>
good_example(corosio::tcp_socket& sock)
{
    std::string msg = "Hello";
    std::ignore =
        co_await sock.write_some(capy::const_buffer(msg.data(), msg.size()));
}

String Invalidation

Be careful when using std::string as buffer storage:

std::string s = "Hello";
capy::mutable_buffer buf(s.data(), s.size());

s += " World"; // May reallocate, invalidating buf!

// Use buf here: UNDEFINED BEHAVIOR

Either:

  • Reserve sufficient capacity upfront

  • Don’t modify the string while the buffer is in use

  • Create a new buffer after modification

Scatter/Gather I/O

Multiple buffers can be used for efficient scatter/gather operations:

Reading into Multiple Buffers (Scatter)

struct message_header
{
    std::uint32_t type;
    std::uint32_t length;
};

message_header header;
char body[1024];

std::array<capy::mutable_buffer, 2> read_bufs = {
    capy::mutable_buffer(&header, sizeof(header)),
    capy::mutable_buffer(body, sizeof(body))};

auto [ec, n] = co_await sock.read_some(read_bufs);
// Data fills header first, then body

Writing from Multiple Buffers (Gather)

std::string header = "HTTP/1.1 200 OK\r\n\r\n";
std::string body   = "Hello, World!";

std::array<capy::const_buffer, 2> write_bufs = {
    capy::const_buffer(header.data(), header.size()),
    capy::const_buffer(body.data(), body.size())};

auto [ec, n] = co_await sock.write_some(write_bufs);
// Sends header followed by body in a single operation

Example: Reading a Fixed-Size Header

struct packet_header
{
    std::uint32_t magic;
    std::uint32_t length;
};

capy::task<packet_header>
read_header(corosio::io_stream& stream)
{
    packet_header header;
    auto [ec, n] = co_await capy::read(
        stream, capy::mutable_buffer(&header, sizeof(header)));

    if (ec)
        throw std::system_error(ec);

    co_return header;
}

Next Steps