协程支持来自编译器(GCC 12+/Clang 15+/MSVC 2019 16.11+)开启C++20协程特性,以及第三方异步运行时(如boost::asio、cppcoro或libcurl+自封装awaiter)提供网络I/O能力,标准库本身不提供HTTP协程封装。

协程支持从哪来:确认编译器和标准库版本
C++20 的 co_await 本身不提供网络 I/O 能力,它只负责挂起/恢复执行流。真正让 HTTP 异步跑起来的,是底层异步运行时(比如 libcurl + 自定义 awaiter、boost::asio 或 cppcoro)。别指望只靠 std::coroutine_handle 写出能发请求的代码——它连 socket 都不碰。
实操建议:
- 用 GCC 12+ 或 Clang 15+,并开启
-std=c++20 -fcoroutines(GCC)或-std=c++20 -Xclang -enable-experimental-coroutines(Clang) - MSVC 2019 16.11+ 支持较完整,但需加
/std:c++20 /await - 标准库没提供
http_client协程封装,必须自己桥接;std::experimental::coroutine已废弃,别用
如何包装 libcurl 实现可等待的 HTTP 请求
libcurl 是最轻量、跨平台的选择,但它默认是阻塞式。要让它配合协程,关键在于启用 CURLOPT_ASYNC(实际不存在)——不对,libcurl 本身不支持原生异步回调式协程,得靠 CURLOPT_WRITEFUNCTION + curl_multi_socket_action 配合事件循环驱动,再封装成 awaitable。
更可行的做法是:用 curl_easy_setopt(handle, CURLOPT_NOSIGNAL, 1L) 关掉 SIGPIPE,再配合 curl_multi_* API 在一个 asio::io_context 或自建轮询中驱动。但这样写太重。
立即学习“C++免费学习笔记(深入)”;
推荐折中方案:用 libcurl 的 multi 接口 + asio::steady_timer 模拟非阻塞轮询,然后封装为 task<std::string>:
struct http_get_awaiter {
CURL* h;
std::string url;
std::string result;
<pre class='brush:php;toolbar:false;'>bool await_ready() { return false; }
void await_suspend(std::coroutine_handle<> h) {
// 启动 curl_multi,注册 socket 事件回调,把 h 存到 userptr
// ……(省略细节,重点是不能阻塞)
}
std::string await_resume() { return result; }};
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
task<std::string> http_get(std::string url) { co_return co_await http_get_awaiter{nullptr, std::move(url)}; }
常见错误现象:curl_easy_perform 直接调用会阻塞整个协程线程;co_await 返回前没清空 CURL* 导致句柄泄漏;忘记设 CURLOPT_TIMEOUT_MS,协程永远挂起。
asio + C++20 协程:最稳妥的生产级组合
如果你已在用 boost::asio 或 standalone asio(1.78+),它对协程的支持最成熟。asio::awaitable<T> 可直接用于 asio::ip::tcp::socket::async_read 和 async_write,HTTP 就变成手动拼 request、解析 response 的问题。
使用场景:需要细粒度控制连接、重试、超时、SSL 验证,且不希望引入额外依赖。
实操要点:
- 用
asio::use_awaitable替代回调,例如:co_await socket.async_connect(ep, asio::use_awaitable) - HTTP 是文本协议,别用
asio::async_read_until读"\r\n\r\n"—— 它可能跨 buffer 边界,改用循环async_read+ 自己找分隔符 -
asio::ssl::stream的async_handshake也支持use_awaitable,但必须先调set_verify_mode,否则 TLS 握手失败静默失败 - 注意
asio::awaitable默认绑定到当前 executor,跨线程 resume 需显式co_await asio::this_coro::executor切换
别踩这些协程 HTTP 的典型坑
协程不是魔法,它只是语法糖,背后仍是状态机和内存管理。以下问题在真实项目里高频出现:
- 协程栈空间默认很小(通常 8KB),在嵌套多层
co_await或拼大 JSON 时容易栈溢出;用asio::detached或手动分配堆内存避免 -
co_return抛异常?必须确保promise_type::unhandled_exception()正确捕获,否则崩溃;建议统一用std::expected<T, std::string>包装返回值 - HTTP keep-alive 复用连接时,多个协程共用一个
socket必须加锁或序列化访问,否则async_write交错发送请求 - 忘记设置
Connection: close或没处理Transfer-Encoding: chunked,导致co_await async_read永远等不到 EOF
最常被忽略的点:协程生命周期和 socket 生命周期必须对齐。一个 awaitable 对象析构时,如果底层 I/O 还在进行,asio 会 abort,libcurl 会泄露 handle——没有 RAII 封装,就等于没做异步安全。

















