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
capy::read() into a dynamic buffer
A second overload reads until EOF, growing the buffer as needed. Use
capy::string_dynamic_buffer to wrap a std::string:
std::string content;
auto [ec, n] = co_await capy::read(
stream, capy::string_dynamic_buffer(&content));
// On success (EOF reached): ec is default, n is total bytes read
// On error: ec is the error, n is bytes read before the error
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
consuming_buffers Helper
Both read() and write() use consuming_buffers internally to track
progress through a buffer sequence:
#include <boost/capy/buffers/consuming_buffers.hpp>
std::array<capy::mutable_buffer, 2> bufs = {
capy::mutable_buffer(header, 16),
capy::mutable_buffer(body, 1024)
};
capy::consuming_buffers<decltype(bufs)> consuming(bufs);
// After reading 20 bytes:
consuming.consume(20);
// Now consuming represents: 4 bytes of header remaining + full body
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 = {...};
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;
auto [ec, n] = co_await capy::read(
stream, capy::string_dynamic_buffer(&response));
if (ec)
capy::detail::throw_system_error(ec);
co_return response;
}
Next Steps
-
Sockets — The underlying stream interface
-
Buffer Sequences — Working with buffers
-
HTTP Client Tutorial — Practical example