Domain Track
Difficulty 3/5C++ for Networking
Networking in C++ — Asio async I/O, io_uring, HTTP/WebSocket with Crow and Boost.Beast, zero-copy patterns, and high-performance server design.
Asio — the foundation of async I/O
Asio (standalone or Boost.Asio) provides the event loop and async primitives for virtually every C++ network application. It supports callbacks, coroutines (C++20), and executors.
cpp
// Standalone asio (no Boost dependency)
#include <asio.hpp>
int main() {
asio::io_context io;
// Single-threaded: run all handlers on this thread
// Multi-threaded: pool of threads calling io.run()
io.run();
}TCP echo server (coroutines)
cpp
#include <asio.hpp>
#include <asio/co_spawn.hpp>
#include <asio/detached.hpp>
#include <print>
using asio::ip::tcp;
asio::awaitable<void> handle_client(tcp::socket socket) {
char buf[1024];
for (;;) {
// Read data — suspends coroutine until data arrives
std::size_t n = co_await socket.async_read_some(
asio::buffer(buf), asio::use_awaitable);
// Echo it back
co_await asio::async_write(
socket, asio::buffer(buf, n), asio::use_awaitable);
}
}
asio::awaitable<void> accept_loop(tcp::acceptor& acceptor) {
for (;;) {
tcp::socket socket = co_await acceptor.async_accept(asio::use_awaitable);
// Spawn each client as an independent coroutine
co_spawn(acceptor.get_executor(),
handle_client(std::move(socket)), asio::detached);
}
}
int main() {
asio::io_context io;
tcp::acceptor acceptor(io, {tcp::v4(), 8080});
co_spawn(io, accept_loop(acceptor), asio::detached);
io.run(); // blocks until all work is done
}TCP client (coroutines)
cpp
asio::awaitable<void> connect_and_send(asio::io_context& io) {
tcp::resolver resolver(io.get_executor());
tcp::socket socket(io.get_executor());
auto endpoints = co_await resolver.async_resolve(
"example.com", "80", asio::use_awaitable);
co_await asio::async_connect(socket, endpoints, asio::use_awaitable);
std::string request = "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n";
co_await asio::async_write(socket, asio::buffer(request), asio::use_awaitable);
std::string response;
co_await asio::async_read_until(socket, asio::dynamic_buffer(response),
"\r\n\r\n", asio::use_awaitable);
std::println("{}", response);
}Multi-threaded server — thread pool
cpp
asio::io_context io;
auto work = asio::make_work_guard(io); // keep io_context alive
// Thread pool — N threads sharing the same io_context
std::vector<std::thread> threads;
int N = std::thread::hardware_concurrency();
for (int i = 0; i < N; ++i) {
threads.emplace_back([&io] { io.run(); });
}
// Handlers are dispatched to any free thread — must be thread-safe!
// Use strand to serialize handlers for a given connection:
asio::strand<asio::io_context::executor_type> strand =
asio::make_strand(io);
// Post work on the strand
asio::post(strand, []{
// Serialized with other work on the same strand
});
// Join
work.reset();
for (auto& t : threads) t.join();HTTP with Crow (micro-framework)
cpp
#include <crow.h>
int main() {
crow::SimpleApp app;
CROW_ROUTE(app, "/")
([] {
return "Hello, World!";
});
CROW_ROUTE(app, "/api/user/<int>")
([](int user_id) {
crow::json::wvalue res;
res["id"] = user_id;
res["name"] = "Alice";
return crow::response{res};
});
CROW_ROUTE(app, "/api/data").methods(crow::HTTPMethod::POST)
([](const crow::request& req) {
auto body = crow::json::load(req.body);
if (!body) return crow::response(400, "Invalid JSON");
auto name = body["name"].s();
return crow::response{200, std::string("Hello ") + name};
});
app.port(8080).multithreaded().run();
}WebSocket with Boost.Beast
cpp
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/asio/co_spawn.hpp>
namespace beast = boost::beast;
namespace websocket = beast::websocket;
namespace net = boost::asio;
using tcp = net::ip::tcp;
net::awaitable<void> ws_session(tcp::socket socket) {
websocket::stream<tcp::socket> ws{std::move(socket)};
// Perform WebSocket handshake
co_await ws.async_accept(net::use_awaitable);
beast::flat_buffer buf;
for (;;) {
co_await ws.async_read(buf, net::use_awaitable);
ws.text(ws.got_text());
co_await ws.async_write(buf.data(), net::use_awaitable);
buf.consume(buf.size());
}
}HTTP client with Boost.Beast
cpp
net::awaitable<std::string> http_get(std::string host, std::string target) {
auto executor = co_await net::this_coro::executor;
tcp::resolver resolver{executor};
beast::tcp_stream stream{executor};
auto results = co_await resolver.async_resolve(host, "80", net::use_awaitable);
co_await stream.async_connect(results, net::use_awaitable);
beast::http::request<beast::http::empty_body> req{
beast::http::verb::get, target, 11};
req.set(beast::http::field::host, host);
req.set(beast::http::field::user_agent, "beast/1.0");
co_await beast::http::async_write(stream, req, net::use_awaitable);
beast::flat_buffer buf;
beast::http::response<beast::http::dynamic_body> res;
co_await beast::http::async_read(stream, buf, res, net::use_awaitable);
co_return beast::buffers_to_string(res.body().data());
}io_uring — Linux kernel async I/O (C++20)
cpp
#include <liburing.h>
#include <fcntl.h>
// io_uring: submits I/O requests to the kernel; kernel completes them async
// Up to 2× throughput vs epoll for mixed read/write workloads
struct io_uring ring;
io_uring_queue_init(256, &ring, 0); // 256 submission queue entries
// --- Submit a read ---
int fd = open("data.bin", O_RDONLY | O_DIRECT);
char* buf;
posix_memalign((void**)&buf, 4096, 4096); // aligned for O_DIRECT
struct io_uring_sqe* sqe = io_uring_get_sqe(&ring);
io_uring_prep_read(sqe, fd, buf, 4096, 0);
sqe->user_data = (uint64_t)buf; // tag for result matching
io_uring_submit(&ring);
// --- Collect completion ---
struct io_uring_cqe* cqe;
io_uring_wait_cqe(&ring, &cqe);
if (cqe->res < 0)
fprintf(stderr, "Read error: %s\n", strerror(-cqe->res));
else
printf("Read %d bytes\n", cqe->res);
io_uring_cqe_seen(&ring, cqe);
io_uring_queue_exit(&ring);io_uring with Asio (liburing backend, Asio 1.28+)
cpp
// Asio has native io_uring backend on Linux (much simpler than raw liburing)
asio::io_context io(1); // io_uring backend selected automatically on Linux
// All async_read, async_write operations use io_uring under the hood
// No code change needed — just link with -luringZero-copy with scatter/gather I/O
cpp
// readv/writev — read/write multiple buffers in one syscall
#include <sys/uio.h>
struct iovec iov[3];
char header[16], body[1024], footer[4];
iov[0] = { header, sizeof(header) };
iov[1] = { body, sizeof(body) };
iov[2] = { footer, sizeof(footer) };
ssize_t n = readv(fd, iov, 3); // one syscall fills all three buffers
// sendfile — zero-copy file to socket (no user-space buffer)
#include <sys/sendfile.h>
off_t offset = 0;
sendfile(socket_fd, file_fd, &offset, file_size);UDP and multicast
cpp
using udp = asio::ip::udp;
// UDP sender
udp::socket sender(io, udp::endpoint(udp::v4(), 0));
udp::endpoint target(asio::ip::address::from_string("239.0.0.1"), 9999);
sender.send_to(asio::buffer("hello"), target);
// UDP receiver — multicast join
udp::socket recv_sock(io, udp::endpoint(udp::v4(), 9999));
recv_sock.set_option(asio::ip::multicast::join_group(
asio::ip::address::from_string("239.0.0.1")));
char buf[512];
udp::endpoint sender_ep;
recv_sock.receive_from(asio::buffer(buf), sender_ep);Performance patterns
| Technique | Impact | When to use |
|---|---|---|
TCP_NODELAY | Latency ↓ | Disable Nagle for request-response |
SO_REUSEPORT | Throughput ↑ | Multiple sockets on same port (kernel load balances) |
SO_RCVBUF/SO_SNDBUF | Throughput ↑ | Bulk transfer — increase kernel buffers |
| Strand per connection | Correctness | Avoid locks in multi-thread server |
sendfile / splice | CPU ↓ | Static file serving — zero copy |
io_uring batch | Throughput ↑ | High-IOPS servers on Linux 5.1+ |
MSG_ZEROCOPY | CPU ↓ | Large sends — avoid copy to kernel |
cpp
// Common socket options via Asio
socket.set_option(asio::ip::tcp::no_delay(true)); // TCP_NODELAY
socket.set_option(asio::socket_base::reuse_address(true));
socket.set_option(asio::socket_base::receive_buffer_size(1 << 20)); // 1 MB
socket.set_option(asio::socket_base::send_buffer_size(1 << 20));