Stream Concepts
This page explains Capy’s stream concepts for generic I/O operations. Understanding the distinction between partial and complete operations is key to writing correct network code.
Code snippets assume using namespace boost::capy; is in effect.
|
Stream vs Source/Sink
Capy distinguishes between two levels of I/O abstraction:
| Level | Concepts | Behavior |
|---|---|---|
Partial |
|
Single operation, may transfer less than requested |
Complete |
|
Loops internally, fills/drains buffers completely |
The stream concepts (read_some/write_some) map closely to OS system calls.
The source/sink concepts (read/write) build loops around partial operations.
ReadStream
A ReadStream provides partial read operations via read_some():
template<typename T>
concept ReadStream = requires(T& stream, MutableBufferSequence auto buffers)
{
{ stream.read_some(buffers) } -> IoAwaitable;
// Returns io_result<std::size_t>
};
Behavior
read_some() reads at least one byte (unless EOF or error) but may
read fewer bytes than the buffer can hold:
char buffer[1024];
auto [ec, n] = co_await stream.read_some(mutable_buffer(buffer, 1024));
// n might be 100, even though buffer holds 1024
This matches OS behavior—recv() returns whatever data is available.
WriteStream
A WriteStream provides partial write operations via write_some():
template<typename T>
concept WriteStream = requires(T& stream, ConstBufferSequence auto buffers)
{
{ stream.write_some(buffers) } -> IoAwaitable;
// Returns io_result<std::size_t>
};
Behavior
write_some() writes at least one byte (unless error) but may write
fewer bytes than provided:
std::string data = /* 10000 bytes */;
auto [ec, n] = co_await stream.write_some(make_buffer(data));
// n might be 8192, even though data has 10000 bytes
This matches OS behavior—send() may accept only part of your data.
ReadSource
A ReadSource provides complete read operations via read():
template<typename T>
concept ReadSource = requires(T& source, MutableBufferSequence auto buffers)
{
{ source.read(buffers) } -> IoAwaitable;
// Returns io_result<std::size_t>
};
Behavior
read() fills the buffer completely before returning, looping as needed:
char header[16];
auto [ec, n] = co_await source.read(mutable_buffer(header, 16));
// If successful: n == 16 (always fills completely)
WriteSink
A WriteSink provides complete write operations with EOF signaling:
template<typename T>
concept WriteSink = requires(T& sink, ConstBufferSequence auto buffers, bool eof)
{
{ sink.write(buffers) } -> IoAwaitable; // Write all bytes
{ sink.write(buffers, eof) } -> IoAwaitable; // Write + optional EOF
{ sink.write_eof() } -> IoAwaitable; // Signal end of data
};
Behavior
write() sends all bytes before returning:
std::string body = /* 10000 bytes */;
auto [ec, n] = co_await sink.write(make_buffer(body));
// If successful: n == 10000 (always writes completely)
The EOF variants signal that no more data will follow:
// Option 1: write then signal EOF
co_await sink.write(make_buffer(data));
co_await sink.write_eof();
// Option 2: combined call
co_await sink.write(make_buffer(data), true);
Composed Algorithms
Capy provides read() and write() free functions that build complete
operations from partial ones.
Choosing the Right Level
Use stream concepts (read_some/write_some) when:
-
You want to process data as it arrives
-
You’re implementing a protocol with natural chunk boundaries
-
You need fine-grained control over I/O timing
Use source/sink concepts (read/write) when:
-
You need exactly N bytes before proceeding
-
You’re reading until EOF
-
You want simple "send this, receive that" semantics
Example: Echo Server
// Using partial operations for streaming
task<void> echo_streaming(ReadStream auto& in, WriteStream auto& out)
{
char buffer[4096];
for (;;)
{
auto [ec1, n] = co_await in.read_some(make_buffer(buffer));
if (ec1 == cond::eof)
break;
if (ec1.failed())
co_return;
// Use write() to ensure all bytes are sent
auto [ec2, _] = co_await write(out, make_buffer(buffer, n));
if (ec2.failed())
co_return;
}
}
Summary
| Concept | Method | Behavior |
|---|---|---|
|
|
Read at least one byte (partial) |
|
|
Write at least one byte (partial) |
|
|
Fill buffers completely or EOF |
|
|
Write all bytes, signal EOF |