1 alexprivalov

boost-asio-pro Skill

在使用 Boost.Asio 或独立版 Asio 构建异步网络应用时使用——TCP/UDP 服务器与客户端、SSL/TLS 流、基于 C++20 协程或 C++20 之前回调/有栈协程的异步 I/O、定时器、基于 strand 的并发,以及异步操作组合。当代码涉及 io_context、co_spawn、awaitable、async_read、async_write、strand、asio::spawn、yield_context 或完成回调(C++11 及以上)时使用。

安装方式:把技能目录放入 ~/.claude/skills/(Claude Code)或在 claude.ai 设置中启用;也可复制右侧安装命令一键添加。

查看源码

技能指令原文(SKILL.md)

Boost.Asio / standalone Asio

Write async C++ networking code that compiles on the user's Boost, not the newest one. Asio's API changed shape three times (classic io_service → io_context → C++20 coroutines) and most Asio code on the internet is from the first era, so pick the style from the toolchain first, then follow that style's reference file.

References: Boost.Asio · standalone Asio

Step 1: pick the style (do this before writing code)

Determine the Boost (or Asio) version and the C++ standard actually in use — find_package(Boost) output, dpkg -l libboost-dev, brew info boost, CMAKE_CXX_STANDARD, or ask. Do not assume the newest.

| Boost | C++ std | Style | Read |
|-------|---------|-------|------|
| ≥ 1.77 | C++20 | Coroutines (co_await + awaitable) — preferred | references/coroutines.md |
| ≥ 1.74 | C++11–17 | Completion handlers (callbacks) — the portable baseline | references/pre-cpp20.md |
| ≥ 1.80 | C++11–17 | Stackful asio::spawn + yield_context (links Boost.Coroutine — not header-only) | references/pre-cpp20.md |
| 1.62–1.65 | C++11 | Classic io_service / strand.wrap / expires_from_now | references/classic-boost.md |

SSL/TLS in any style: references/ssl.md. CMake for any style: references/build.md.

io_context, make_strand, bind_executor, steady_timer, signal_set, async_read/async_write/async_read_until, buffers and resolver are library features — identical in the coroutine and callback styles. Only the suspension mechanism differs.

Step 2: version floors (verified by compiling, not from docs)

Reach for one of these and the build breaks on older distros:

| Feature | Floor |
|---------|-------|
| experimental/awaitable_operators.hpp (the \|\| / && operators) | Boost ≥ 1.77 / Asio ≥ 1.20 |
| as_tuple completion token | Boost ≥ 1.79 / Asio ≥ 1.21 |
| co_composed (custom composed ops) | Boost ≥ 1.85 / Asio ≥ 1.30 |
| 3-arg asio::spawn(ex, fn, token) | Boost ≥ 1.80 — pass an explicit token such as asio::detached; avoid the default completion token (older Boost has only spawn(ex, fn)) |
| any_io_executor (strand, tcp::socket's default executor) | Boost ≥ 1.74 — the floor for the callback style; below it, use legacy io_context::strand |
| io_context, make_strand, expires_after | Boost ≥ 1.66 — below it, classic io_service |

Distro floors that bite: Debian bookworm ships Boost 1.74 (no awaitable_operators.hpp — #include fails outright), Ubuntu 20.04 ships 1.71 (no any_io_executor), Debian 9 ships 1.62.

Language, not library: the chrono literals 250ms / 30s are C++14. For a true C++11 build write std::chrono::milliseconds(250).

Step 3: the rules that are actually easy to get wrong

A strand does not serialize writes. A strand serializes handler execution, not whole composed operations. Two async_writes in flight on the same strand still interleave bytes on the wire. Full-duplex (a read loop plus concurrent pushes/replies) needs a per-connection strand and an outbound queue with an in-flight flag, so at most one async_write exists at a time. This is the single most common wrong answer about Asio.

Buffers do not own memory. asio::buffer() is a view. Storage must outlive the operation: coroutine locals are fine across co_await in the same frame; in callback style the same data must become a member, not a local.

Stackful coroutine stacks are small. In asio::spawn / yield_context code, do not allocate large fixed-size objects as coroutine locals. A local such as std::array 1024> can overflow the coroutine stack; default stack sizes are often around 64-128 KiB depending on Boost.Context/platform/configuration. Put large buffers on the heap instead, e.g. std::vector buf(256 1024) or auto buf = std::make_shared>(...). Increasing the coroutine stack with Boost.Coroutine attributes is possible, but prefer heap buffers for large I/O storage.

Connections must outlive their handlers. enable_shared_from_this, and capture self in every co_spawn / handler — read loop, write loop, and each timer.

Frame with composed reads. async_read (fills the buffer exactly) for a length prefix and then the body; never async_read_some, which returns short.

Wrap as_tuple. Always as_tuple(use_awaitable). Bare as_tuple resolves against the operation's default token and compiles in some contexts, fails in others.

async_accept(make_strand(...)) changes two things: it forces an explicit completion token back on the call, and the accepted socket is basic_stream_socket>, not tcp::socket. Take it by value or with auto — binding it to tcp::socket& will not compile.

Re-arming a timer resolves the pending wait with operation_aborted. In an idle-timeout loop that is the signal to keep waiting, not an error.

GCC needs -fcoroutines for the C++20 style, and header-only Boost needs BOOST_ERROR_CODE_HEADER_ONLY defined in exactly one place (CMake).

Start stackful coroutines explicitly. The 2-arg form of asio::spawn only starts on its own in older versions. In newer versions, do not ignore the returned completion token - it must be invoked for the routine to actually run. To start the routine without needing to explicitly wait on it, use the 3-arg form with asio::detached. Remember, if you are not sure which to use, ask or otherwise explicitly note which kind you assumed.

Common mistakes

| Mistake | Fix |
|---------|-----|
| Buffer dangling (local goes out of scope during async op) | Ensure buffer lifetime ≥ operation lifetime; coroutine locals or members, not callback locals |
| Forgetting io.run() | No handlers dispatch without run() / run_one() |
| Concurrent socket access without strand | Wrap in strand<> or serialize via one coroutine chain |
| Assuming a strand prevents interleaved writes | Add a write queue — see Step 3 |
| Using use_awaitable where deferred suffices | Omit the token (default is deferred) unless using \|\| / && |
| Ignoring short reads/writes | Use composed async_read / async_write / async_read_until, not async_read_some |
| Not setting reuse_address on the acceptor | Set before bind/listen or restarts hit "address in use" |
| SSL operations without a strand | All ssl::stream ops need strand synchronization |
| Blocking inside a handler | Never block in a completion handler |
| Accepting a socket with the wrong executor type | See async_accept(make_strand(...)) in Step 3 |
| Requiring the Boost::system component | Header-only since 1.74: Boost::headers + BOOST_ERROR_CODE_HEADER_ONLY. Only classic (pre-1.66) needs the link |
| Missing -fcoroutines on GCC | Build fails — add $<$:-fcoroutines> |
| Writing coroutine code for a Boost that predates it | Do Step 1 first |
| Calling asio::spawn(ex, fn) and ignoring the returned deferred operation | Use asio::spawn(ex, fn, asio::detached) for fire-and-forget stackful coroutines, or deliberately return/await the operation |
| Large std::array or object local inside asio::spawn coroutine | Use heap-backed storage such as std::vector, unique_ptr, shared_ptr, or a session member; stackful coroutine stacks are small |

Boost.Asio vs standalone Asio

Same author, same API — namespace and includes differ.

| Aspect | Boost.Asio | Standalone Asio |
|--------|-----------|-----------------|
| Namespace / include | boost::asio / | asio / |
| Error code | boost::system::error_code | asio::error_code (or std::error_code) |
| Install (brew) | brew install boost | brew install asio |
| CMake | Boost::headers | manual include path |
| Version (2025) | 1.87–1.90 (with Boost) | 1.30–1.36 (independent) |
| Macro prefix | BOOST_ASIO_ | ASIO_ |

Support both with a shim, then use net:: throughout:

#ifdef USE_STANDALONE_ASIO
  #include <asio.hpp>
  namespace net = asio;
  using error_code = asio::error_code;
#else
  #include <boost/asio.hpp>
  namespace net = boost::asio;
  using error_code = boost::system::error_code;
#endif
namespace ssl = net::ssl;
using tcp = net::ip::tcp;

Before you call it done

Check the code you just wrote against this list:

  • [ ] Style matches the target Boost version and C++ standard (Step 1), and every API used clears its floor (Step 2).
  • [ ] Every buffer passed to an async op outlives that op — no callback locals, no dangling string_view.
  • [ ] At most one async_write per socket in flight, enforced by a queue + flag, if anything writes concurrently with reading.
  • [ ] Every async chain on a shared object runs on the same strand; self captured in every handler and co_spawn.
  • [ ] Framing / delimited reads use composed async_read / async_read_until.
  • [ ] Errors are handled, not swallowed: as_tuple(use_awaitable) destructured, or the callback's ec checked, on every op.
  • [ ] operation_aborted distinguished from real errors wherever a timer is re-armed or an op is cancelled.
  • [ ] Acceptor sets reuse_address; shutdown path closes the acceptor and drains sessions.
  • [ ] Stackful spawn examples either pass an explicit completion token such as asio::detached, or intentionally return/initiate the deferred operation.
  • [ ] CMake has the standard, -fcoroutines for GCC (C++20 only), BOOST_ERROR_CODE_HEADER_ONLY in one place, and Boost::coroutine only if using stackful spawn.
  • [ ] It compiles. Build it — most of the mistakes above are compile-time, and the version floors are only real once tested.
  • [ ] Stackful spawn coroutines do not place large buffers or large structs on the coroutine stack; use heap-backed storage for chunk buffers.

Worked examples

Three CI-verified implementations of the same full-duplex framed-protocol server, one per style — copy from the one matching Step 1. Paths are relative to this skill directory; if only the skill was installed, they are at https://github.com/alexprivalov/boost-asio-skill/tree/main/examples.

  • ../../examples/market-data-feed/ — C++20 coroutines (Boost 1.77+; verified 1.83–1.90)
  • ../../examples/market-data-feed-precpp20/ — callbacks, C++11-clean (verified Boost 1.74+, incl. Windows/MSVC)
  • ../../examples/market-data-feed-classic/ — classic io_service (verified back to Boost 1.62 / Debian 9)

Official documentation

  • Overview: https://www.boost.org/doc/libs/latest/doc/html/boost_asio/overview.html
  • Reference: https://www.boost.org/doc/libs/latest/doc/html/boost_asio/reference.html
  • Examples: https://www.boost.org/doc/libs/latest/doc/html/boost_asio/examples.html