Composed Operations
Corosio provides composed operations that build on the primitive read_some()
and write_some() functions to provide higher-level guarantees.
|
Code snippets assume:
|
The Problem with Primitives
The primitive operations read_some() and write_some() provide no guarantees
about how much data is transferred:
char buf[1024];
auto [ec, n] = co_await s.read_some(
capy::mutable_buffer(buf, sizeof(buf)));
// n could be 1, 100, 500, or 1024 - no guarantee
For many use cases, you need to transfer a specific amount of data. Composed operations provide these guarantees.
capy::read()
The read() function reads until the buffer is full or an error occurs:
char buf[1024];
auto [ec, n] = co_await capy::read(
stream, capy::mutable_buffer(buf, sizeof(buf)));
// Either:
// - n == 1024 and ec is default (success)
// - ec == capy::cond::eof and n < 1024 (reached end of stream)
// - ec is some other error
Reading Until EOF
To consume a stream of unknown length, loop over read_some() into a
fixed-size chunk and grow a std::string as data arrives:
std::string content;
char chunk[2048];
for (;;)
{
auto [ec, n] = co_await stream.read_some(
capy::mutable_buffer(chunk, sizeof(chunk)));
content.append(chunk, n);
if (ec == capy::cond::eof)
break; // success: the whole stream was consumed
if (ec)
throw std::system_error(ec);
}
capy::write()
The write() function writes all data or fails:
std::string msg = "Hello, World!";
auto [ec, n] = co_await capy::write(
stream, capy::const_buffer(msg.data(), msg.size()));
// Either:
// - n == msg.size() and ec is default (all data written)
// - ec is an error
buffer_slice Helper
Both read() and write() use capy::consuming_buffers internally to
track progress through a buffer sequence. A related helper,
capy::buffer_slice, returns a view over a byte range of the underlying
sequence. The result is itself a buffer sequence covering only that
range, so it can be passed directly to any I/O operation:
#include <boost/capy/buffers/buffer_slice.hpp>
std::array<capy::mutable_buffer, 2> bufs = {
capy::mutable_buffer(header, 16),
capy::mutable_buffer(body, 1024)
};
// After reading 20 bytes, slice off what was consumed:
auto rest = capy::buffer_slice(bufs, 20);
// rest covers: 4 bytes of header remaining + full body
Interface
template<class BufferSequence>
requires MutableBufferSequence<BufferSequence>
|| ConstBufferSequence<BufferSequence>
slice_type<BufferSequence>
buffer_slice(
BufferSequence const& seq,
std::size_t offset = 0,
std::size_t length = (std::numeric_limits<std::size_t>::max)());
The returned slice_type models the same buffer-sequence concept as
seq (mutable when seq is a mutable buffer sequence): slicing a
single buffer yields an adjusted buffer of the same kind, while any
other sequence yields a borrowed view. offset is clamped to the total
size, and length defaults to the end of the sequence.
Except for the single-buffer case, the result borrows seq: it
stores iterators into the sequence, not a copy, so seq must outlive
it. Passing a temporary buffer sequence is rejected at compile time;
hoist it into a named variable first.
|
Error Handling Patterns
Structured Bindings with EOF Check
auto [ec, n] = co_await capy::read(stream, buf);
if (ec)
{
if (ec == capy::cond::eof)
std::cout << "End of stream, read " << n << " bytes\n";
else
std::cerr << "Error: " << ec.message() << "\n";
}
Exception Pattern
// For write (EOF doesn't apply)
auto [wec, n] = co_await capy::write(stream, buf);
if (wec)
capy::detail::throw_system_error(wec);
// For read (need to handle EOF)
auto [rec, rn] = co_await capy::read(stream, buf);
if (rec && rec != capy::cond::eof)
capy::detail::throw_system_error(rec);
Cancellation
Composed operations support cancellation through the affine protocol. When
cancelled, they return with cond::canceled and the partial byte count.
auto [ec, n] = co_await capy::read(stream, large_buffer);
if (ec == capy::cond::canceled)
std::cout << "Cancelled after reading " << n << " bytes\n";
Performance Considerations
Single vs. Multiple Buffers
For optimal performance with multiple buffers:
// Efficient: single system call per read_some()
std::array<capy::mutable_buffer, 2> bufs = {
capy::mutable_buffer(header, 16),
capy::mutable_buffer(body, 1024)};
co_await capy::read(stream, bufs);
// Less efficient: may require more system calls
co_await capy::read(stream, buf1);
co_await capy::read(stream, buf2);
Example: HTTP Response Reading
capy::task<std::string> read_http_response(corosio::io_stream& stream)
{
std::string response;
char chunk[2048];
for (;;)
{
auto [ec, n] = co_await stream.read_some(
capy::mutable_buffer(chunk, sizeof(chunk)));
response.append(chunk, n);
if (ec == capy::cond::eof)
break;
if (ec)
throw std::system_error(ec);
}
co_return response;
}
Next Steps
-
Sockets — The underlying stream interface
-
Buffer Sequences — Working with buffers
-
HTTP Client Tutorial — Practical example