Buffers and I/O
This page explains why buffers exist and how they enable efficient I/O operations in network programming.
Code snippets assume using namespace boost::capy; is in effect.
|
Why Buffers?
Every I/O operation ultimately moves bytes between memory and an external resource—a socket, file, or device. The operating system reads and writes bytes from contiguous memory regions. A buffer is simply a reference to such a region: a pointer and a size.
// The fundamental pattern: pointer + size
void const* data = ...;
std::size_t size = ...;
// This is what OS system calls need
send(socket, data, size, flags);
Capy’s buffer types wrap this pattern with type safety and convenience.
Platform-Agnostic I/O
Capy’s I/O algorithms work against concepts rather than concrete types.
The read() and write() functions don’t care whether you’re using:
-
A real TCP socket
-
An SSL/TLS encrypted stream
-
A mock object for unit testing
-
A custom stream implementation
// This function works with ANY stream type
template<ReadStream Stream>
task<std::string> read_message(Stream& stream)
{
char header[4];
auto [ec, n] = co_await read(stream, mutable_buffer(header, 4));
if (ec.failed())
co_return {};
std::uint32_t len = decode_length(header);
std::string body(len, '\0');
auto [ec2, n2] = co_await read(stream, mutable_buffer(body.data(), len));
co_return body;
}
This means you can test network code without network I/O:
// In production: real socket
task<> handle_client(tcp::socket& sock)
{
auto msg = co_await read_message(sock);
}
// In tests: mock stream
task<> test_read_message()
{
test::read_stream mock("\\x00\\x00\\x00\\x05Hello");
auto msg = co_await read_message(mock);
assert(msg == "Hello");
}
Scatter/Gather I/O
Real protocols rarely transmit data as a single contiguous block. Consider an HTTP response:
HTTP/1.1 200 OK\r\n
Content-Type: text/plain\r\n
Content-Length: 13\r\n
\r\n
Hello, World!
The headers come from one place (perhaps formatted on the stack), while the body comes from another (perhaps a file or database). Copying everything into a single buffer wastes time and memory.
Scatter/gather I/O solves this with multiple buffers in a single operation.
Gather Write
Write from multiple non-contiguous memory regions in one call:
std::string headers = format_headers(status, content_type, body.size());
std::string_view body = get_response_body();
// Gather write: two buffers, one operation
std::array<const_buffer, 2> buffers = {
const_buffer(headers.data(), headers.size()),
const_buffer(body.data(), body.size())
};
auto [ec, n] = co_await write(stream, buffers);
The stream writes both regions without copying them together first.
Scatter Read
Read into multiple buffers in one operation:
// WebSocket frame: 2-byte header + up to 125-byte payload
char header[2];
char payload[125];
std::array<mutable_buffer, 2> buffers = {
mutable_buffer(header, 2),
mutable_buffer(payload, 125)
};
auto [ec, n] = co_await stream.read_some(buffers);
// Data filled header first, then payload
Real-World Example: WebSocket Frame
A WebSocket frame has a variable-length header followed by payload:
task<io_result<std::size_t>> send_frame(
WriteStream auto& stream,
std::uint8_t opcode,
std::span<char const> payload)
{
// Build the frame header
char header[2];
header[0] = 0x80 | opcode; // FIN + opcode
header[1] = static_cast<char>(payload.size());
// Gather write: header + payload
std::array<const_buffer, 2> frame = {
const_buffer(header, 2),
const_buffer(payload.data(), payload.size())
};
co_return co_await write(stream, frame);
}
No intermediate buffer, no copying—just efficient I/O.
The Buffer Abstraction Layers
Capy provides buffers at multiple abstraction levels:
| Level | Types |
|---|---|
Single buffers |
|
Buffer sequences |
Arrays, |
Dynamic buffers |
|
As you move up the abstraction ladder, you trade some control for convenience. Start with the simplest tool that fits your needs.
Summary
-
Buffers represent contiguous memory regions for I/O
-
Buffer sequences enable scatter/gather I/O without copying
-
I/O algorithms work against concepts, not concrete types
-
The same code works with real streams, mock objects, and custom implementations