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: |
#include <boost/corosio/read.hpp>
#include <boost/corosio/write.hpp>
#include <boost/capy/buffers.hpp>
namespace corosio = boost::corosio;
namespace capy = boost::capy;
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.
corosio::read()
The read() function reads until the buffer is full or an error occurs:
char buf[1024];
auto [ec, n] = co_await corosio::read(
stream, capy::mutable_buffer(buf, sizeof(buf)));
// Either:
// - n == 1024 and ec is default (success)
// - ec is error::eof and n < 1024 (reached end of stream)
// - ec is some other error
corosio::read() into std::string
A special overload reads until EOF, growing the string as needed:
std::string content;
auto [ec, n] = co_await corosio::read(stream, content);
// Either:
// - ec == capy::error::eof (normal termination)
// - ec is some error
// content contains all data read
corosio::write()
The write() function writes all data or fails:
std::string msg = "Hello, World!";
auto [ec, n] = co_await corosio::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/corosio/consuming_buffers.hpp>
std::array<capy::mutable_buffer, 2> bufs = {
capy::mutable_buffer(header, 16),
capy::mutable_buffer(body, 1024)
};
corosio::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
Cancellation
Composed operations support cancellation through the affine protocol. When
cancelled, they return with operation_canceled and the partial byte count.
auto [ec, n] = co_await corosio::read(stream, large_buffer);
if (ec == make_error_code(system::errc::operation_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 corosio::read(stream, bufs);
// Less efficient: may require more system calls
co_await corosio::read(stream, buf1);
co_await corosio::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 corosio::read(stream, response);
// EOF is expected when server closes connection
if (ec && ec != capy::error::eof)
throw boost::system::system_error(ec);
co_return response;
}
Next Steps
-
Sockets — The underlying stream interface
-
Buffer Sequences — Working with buffers
-
HTTP Client Tutorial — Practical example