Quick Start

This guide walks you through building your first network application with Corosio: a simple echo server that accepts connections and echoes back whatever clients send.

Code snippets assume:
#include <boost/corosio.hpp>
#include <boost/corosio/acceptor.hpp>
#include <boost/capy/task.hpp>
#include <boost/capy/ex/run_async.hpp>
#include <boost/capy/buffers.hpp>

namespace corosio = boost::corosio;
namespace capy = boost::capy;

Step 1: Create the I/O Context

Every Corosio program starts with an io_context. This is the event loop that processes all asynchronous operations:

int main()
{
    corosio::io_context ioc;

    // ... launch coroutines ...

    ioc.run();  // Process events until all work completes
}

The run() method blocks and processes events until there’s no more work.

Step 2: Create an Acceptor

The acceptor listens for incoming TCP connections:

corosio::acceptor acc(ioc);
acc.listen(corosio::endpoint(8080));  // Listen on port 8080

The endpoint(port) constructor binds to all network interfaces on the specified port.

Step 3: Write the Echo Session

Each client connection is handled by a coroutine that reads data and echoes it back:

capy::task<void> run_session(corosio::socket sock)
{
    char buf[1024];

    for (;;)
    {
        // Read some data
        auto [ec, n] = co_await sock.read_some(
            capy::mutable_buffer(buf, sizeof(buf)));

        if (ec || n == 0)
            break;  // Connection closed or error

        // Echo it back
        auto [wec, wn] = co_await corosio::write(
            sock, capy::const_buffer(buf, n));

        if (wec)
            break;  // Write error
    }

    sock.close();
}

Key points:

  • read_some() returns when any data is available

  • write() (the free function) writes all data or fails

  • Structured bindings extract the error code and byte count

Step 4: Write the Accept Loop

The accept loop waits for connections and spawns sessions:

capy::task<void> accept_loop(
    corosio::io_context& ioc,
    corosio::acceptor& acc)
{
    for (;;)
    {
        corosio::socket peer(ioc);
        auto [ec] = co_await acc.accept(peer);

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

        // Spawn the session coroutine
        capy::run_async(ioc.get_executor())(run_session(std::move(peer)));
    }
}

The run_async function launches a coroutine with executor affinity. Each spawned session runs concurrently with the accept loop.

Step 5: Put It Together

int main()
{
    corosio::io_context ioc;

    corosio::acceptor acc(ioc);
    acc.listen(corosio::endpoint(8080));

    std::cout << "Echo server listening on port 8080\n";

    capy::run_async(ioc.get_executor())(accept_loop(ioc, acc));

    ioc.run();
}

Testing the Server

Start the server, then use netcat or telnet to test:

$ telnet localhost 8080
Trying 127.0.0.1...
Connected to localhost.
Hello, World!
Hello, World!

Error Handling Patterns

Corosio supports two error handling patterns:

auto [ec, n] = co_await sock.read_some(buf);
if (ec)
{
    // Handle error
}

Exceptions (Concise for Simple Cases)

auto n = (co_await sock.read_some(buf)).value();
// Throws system_error if read fails

The .value() method throws boost::system::system_error if the operation failed.

Next Steps

Now that you have a working echo server: