Buffer Sequences
Corosio I/O operations work with buffer sequences from Boost.Capy. This page explains how to use buffers effectively.
|
Code snippets assume:
|
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);
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
-
Composed Operations — Using buffers with read/write
-
Sockets — Socket I/O operations
-
Echo Server Tutorial — Practical usage