Sockets

The socket class provides asynchronous TCP socket operations that return awaitables. Each operation participates in the affine awaitable protocol, ensuring your coroutine resumes on the correct executor.

Code snippets assume the following declarations are in effect:
#include <boost/corosio.hpp>
namespace corosio = boost::corosio;

Creating a Socket

Sockets are created from an execution context or executor:

corosio::io_context ioc;
corosio::socket s(ioc);            // From io_context
corosio::socket s2(ioc.get_executor());  // From executor

Sockets are move-only. Moving a socket between execution contexts is not allowed and throws std::logic_error.

Opening and Closing

Before performing I/O, open the socket:

s.open();  // Creates IPv4 TCP socket

The socket registers with the platform reactor (IOCP) during open(). Call close() to release resources:

s.close();  // Cancels pending ops, releases handle

Check if a socket is open:

if (s.is_open())
    // ...

Connecting

The connect() function initiates an asynchronous connection:

corosio::endpoint ep(boost::urls::ipv4_address::loopback(), 8080);
auto ec = co_await s.connect(ep);

if (ec)
{
    std::cerr << "Connect failed: " << ec.message() << "\n";
    co_return;
}

The awaitable returns a boost::system::error_code. Common errors:

  • errc::connection_refused — No server listening

  • errc::timed_out — Connection attempt timed out

  • errc::network_unreachable — No route to host

Reading Data

The read_some() function reads available data into a buffer:

char buf[1024];
auto [ec, n] = co_await s.read_some(
    boost::buffers::mutable_buffer(buf, sizeof(buf)));

if (ec)
{
    if (ec == capy::cond::eof)
        std::cout << "Peer closed connection\n";
    else
        std::cerr << "Read error: " << ec.message() << "\n";
    co_return;
}

std::cout << "Read " << n << " bytes\n";

The awaitable returns a pair: {error_code, bytes_transferred}.

read_some() returns when any data is available. It may return fewer bytes than the buffer size. To read an exact amount, loop until you have enough:

capy::task<std::pair<boost::system::error_code, std::size_t>>
read_exactly(corosio::socket& s, char* buf, std::size_t len)
{
    std::size_t total = 0;
    while (total < len)
    {
        auto [ec, n] = co_await s.read_some(
            boost::buffers::mutable_buffer(buf + total, len - total));

        if (ec)
            co_return {ec, total};

        if (n == 0)
            co_return {make_error_code(capy::error::eof), total};

        total += n;
    }
    co_return {{}, total};
}

Writing Data

The write_some() function writes data from a buffer:

const char* msg = "Hello, world!";
auto [ec, n] = co_await s.write_some(
    boost::buffers::const_buffer(msg, std::strlen(msg)));

if (ec)
{
    std::cerr << "Write error: " << ec.message() << "\n";
    co_return;
}

std::cout << "Wrote " << n << " bytes\n";

Like read_some(), this may write fewer bytes than requested. Loop for complete writes:

capy::task<boost::system::error_code>
write_all(corosio::socket& s, const char* data, std::size_t len)
{
    std::size_t total = 0;
    while (total < len)
    {
        auto [ec, n] = co_await s.write_some(
            boost::buffers::const_buffer(data + total, len - total));

        if (ec)
            co_return ec;

        total += n;
    }
    co_return {};
}

Buffer Sequences

Both read_some() and write_some() accept any buffer sequence satisfying boost::buffers::MutableBufferSequence or boost::buffers::ConstBufferSequence. Use multiple buffers for scatter/gather I/O:

std::array<boost::buffers::mutable_buffer, 2> bufs = {
    boost::buffers::mutable_buffer(header, sizeof(header)),
    boost::buffers::mutable_buffer(body, body_size)
};
auto [ec, n] = co_await s.read_some(bufs);

Cancellation

Cancel pending operations with cancel():

s.cancel();  // All pending ops complete with operation_canceled

Operations also support cancellation via std::stop_token through the affine awaitable protocol. When the stop token is triggered, the operation completes immediately with errc::operation_canceled.

Error Handling

Socket operations return error codes rather than throwing exceptions. This design allows efficient error handling in hot paths:

auto [ec, n] = co_await s.read_some(buf);

if (ec)
{
    if (ec == capy::cond::eof)
    {
        // Normal disconnect (end of stream)
    }
    else if (ec == capy::cond::canceled)
    {
        // We called cancel()
    }
    else
    {
        // Log unexpected error
        std::cerr << "Socket error: " << ec.message() << "\n";
    }
    co_return;
}

Thread Safety

A single socket must not have concurrent operations of the same type. You may have one read and one write in flight simultaneously, but not two reads or two writes.

For thread safety across the socket object itself, ensure operations are serialized (e.g., through the executor’s strand semantics if available).

Next Steps