c++如何实现一个简单的协程网络库_c++ C++20 coroutine与asio结合【实战】

C++20协程+Asio可实现直观同步风格的网络库:通过use_awaitable直接co_await异步操作;用when_all组合connect与超时;session协程自动管理生命周期;co_spawn驱动accept循环,无需回调嵌套。

用 C++20 协程 + Asio 实现一个轻量协程网络库,核心不是“造轮子”,而是把 co_await 和 Asio 的异步操作自然衔接起来,让 TCP 连接、读写、超时等逻辑写得像同步代码一样直观。

1. 基础:把 asio 的 async_xxx 封装成可 co_await 的 awaitable

Asio 本身不直接返回 awaitable,但提供了 use_awaitable 令牌(C++20 模式下需启用 ASIO_ENABLE_BOOST_BIND_HPP 或使用 Asio 1.28+ 的原生协程支持)。最简封装方式是:

✅ 推荐做法(Asio ≥ 1.26,启用 ASIO_ENABLE_COROUTINES):

  • 在编译时定义 ASIO_ENABLE_COROUTINES(或用 asio::use_awaitable
  • 所有支持 async 的对象(如 tcp::socket, steady_timer)可直接 co_await socket.async_read_some(..., use_awaitable)
  • 无需手写 await_suspend/await_resume,Asio 内部已实现

⚠️ 注意:确保 io_context 运行在支持协程的线程中(比如用 co_spawn 启动协程)。

2. 写一个协程化的 TCP 连接器(connect_with_timeout)

这是典型场景:连接带超时,失败/超时都统一处理,不用回调嵌套。

示例代码结构:

task connect_with_timeout(
    tcp::socket& sock,
    const tcp::endpoint& ep,
    std::chrono::milliseconds timeout_ms) {
  auto ex = sock.get_executor();
  steady_timer timer{ex, timeout_ms};

  // 并发等待 connect 或 timer
  auto [ec1, ec2] = co_await when_all(
      [&]()->task {
        co_return co_await sock.async_connect(ep, use_awaitable);
      }(),
      [&]()->task {
        co_await timer.async_wait(use_awaitable);
        co_return make_error_code(errc::timed_out);
      }()
  );

  if (!ec1) co_return ec1;  // connect 成功
  if (!ec2) {               // timer 触发,主动关闭 socket
    boost::system::error_code ignored;
    sock.close(ignored);
    co_return ec2;
  }
  co_return ec1 ? ec1 : ec2;
}

? 关键点:when_all 是 Asio 提供的协程组合子(需 #include ),类似 Go 的 select。

3. 协程化会话(session):读-处理-写的闭环

每个连接启动一个协程,生命周期清晰,错误可自然传播:

task session(tcp::socket sock) {
  try {
    char buf[1024];
    for (;;) {
      auto [ec, n] = co_await sock.async_read_some(
          asio::buffer(buf), use_awaitable);
      if (ec == asio::error::eof) break;
      if (ec) throw std::system_error{ec};

      // 处理请求(例如 echo)
      auto [wec, wn] = co_await sock.async_write_some(
          asio::buffer(buf, n), use_awaitable);
      if (wec) throw std::system_error{wec};
    }
  } catch (const std::exception& e) {
    // 自动清理:sock 离开作用域自动关闭
  }
}

✅ 优势:无手动资源管理、无状态机、异常即断连、栈语义清晰。

4. 启动服务:accept 循环用 co_spawn 驱动

避免阻塞主线程,也不用 while+poll,用协程“挂起等待”新连接:

task server_loop(tcp::acceptor& acceptor) {
  for (;;) {
    tcp::socket sock{acceptor.get_executor()};
    auto [ec] = co_await acceptor.async_accept(sock, use_awaitable);
    if (ec) continue;

    // 每个连接派生独立协程,不阻塞 accept 循环
    co_spawn(acceptor.get_executor(),
        [=]() mutable -> task { co_await session(std::move(sock)); },
        detached);
  }
}

// 启动入口
int main() {
  asio::io_context ioc;
  tcp::acceptor acceptor{ioc, {tcp::v4(), 8080}};
  
  co_spawn(ioc, [&]()->task { co_await server_loop(acceptor); }, detached);
  ioc.run();
}

? 提示:生产环境建议加连接数限制、缓冲区复用、日志和错误监控,但骨架已具备可维护性。

基本上就这些——没有宏魔法,不依赖第三方协程调度器,纯 Asio + C++20 标准特性。难点不在语法,而在理解 executor 绑定、协程生命周期与 io_context 的协作关系。写熟几个 awaitable 封装后,网络逻辑反而比传统回调更直白。