协程需C++20及GCC10+/Clang13+/MSVC19.30+支持并启用对应编译选项;HTTP-GET须用非阻塞I/O封装socket;错误处理需覆盖DNS、连接、HTTP层;awaiter对象生命周期必须长于协程帧。

协程环境准备:C++20标准与编译器支持必须明确
直接上手写 co_await 之前,先确认你的工具链是否真能跑通协程——这不是“写了就能用”的功能。GCC 10+、Clang 13+、MSVC 19.30+ 才提供基本可用的协程支持,且默认不开启。GCC 需加 -std=c++20 -fcoroutines,Clang 要 -std=c++20 -stdlib=libc++ -fcoroutines-ts(注意 TS 后缀),MSVC 则依赖 /std:c++20 /await。漏掉任一标志,编译器会静默禁用协程语法,或报出类似 ‘co_await’ is not allowed in this context 的模糊错误。
更隐蔽的问题是:libstdc++ 在 GCC 12 之前不提供 std::experimental::coroutine_handle 的完整实现,而很多协程网络库(如 cppcoro)依赖它。建议优先用 libc++(Clang)或 MSVC,避免在 GCC 上卡在底层类型缺失上。
HTTP-GET协程封装:绕开阻塞式socket调用是关键
协程不是魔法,connect()、recv() 这类系统调用仍是阻塞的。直接在协程里调用它们,整个线程就卡死了。必须把 socket 操作转为非阻塞 + epoll/kqueue/IOCP 等事件驱动机制,并让协程挂起等待事件就绪。
可行路径只有两条:
立即学习“C++免费学习笔记(深入)”;
- 用现成异步 I/O 库封装:如
cppcoro::tcp_socket+cppcoro::io_service,或boost::asio::use_awaitable(需 Boost 1.76+) - 自己基于
poll()/epoll_wait()实现 awaiter:监听 socket 可读/可写事件,返回std::suspend_always直到事件触发
示例(cppcoro 简化版):
cppcoro::task<std::string> http_get(std::string url) {
auto socket = co_await cppcoro::tcp_socket::connect("httpbin.org", "80");
co_await socket.write("GET /get HTTP/1.1\r\nHost: httpbin.org\r\n\r\n");
std::string response;
response.resize(4096);
auto n = co_await socket.read(response.data(), response.size());
response.resize(n);
co_return response;
}
注意:socket.read() 内部必须是非阻塞 recv + 事件循环调度,否则 co_await 就没意义。
错误处理与超时:协程中不能忽略 errno 和状态码
网络请求失败不会抛异常,而是通过返回值或状态码体现。协程里若只检查 co_await socket.connect() 是否成功,会漏掉 DNS 解析失败、连接被拒、SSL 握手失败等场景。
实际要检查的点:
- DNS 查询结果:用
getaddrinfo()异步封装,失败时co_return带错误码的空响应 - connect() 返回 -1 且
errno == EINPROGRESS是正常非阻塞行为;但后续poll()超时或返回POLLHUP就得终止 - HTTP 层解析:响应头里的
Content-Length或Transfer-Encoding: chunked必须正确处理,否则co_await socket.read()可能永远挂起 - 超时必须由外部事件循环控制:给每个
co_await加 timeout wrapper,比如co_await cppcoro::with_timeout(socket.read(...), 5s)
常见陷阱:把 std::this_thread::sleep_for() 放进协程里——这会阻塞整个线程,违背轻量级初衷。
内存与生命周期:awaiter 对象必须比协程帧活得久
协程挂起时,awaiter 对象(比如你写的 read_awaiter)会被存入协程帧中。如果这个对象是栈上临时变量,挂起后立即析构,恢复时访问野指针,崩溃是必然的。
安全做法只有两种:
- awaiter 设计为无状态(仅含 socket fd 和缓冲区指针),所有状态存在 socket 对象内部(如
tcp_socket类自己管理 pending read) - 若需额外状态(如超时定时器),用
std::shared_ptr持有,确保其生命周期覆盖协程整个生命周期
典型错误写法:
// ❌ 错误:awaiter 是局部对象,挂起后销毁 auto awaiter = socket.make_read_awaiter(buf, len); co_await awaiter; // 挂起后 awaiter 析构,恢复时 UB
真正可靠的模式是让 socket 类直接返回 task 或 awaitable,把生命周期管理收束到类内部。
协程的轻量,全靠编译器生成的极小栈帧和无锁调度,但一旦碰上裸指针、栈对象、手动内存管理,优势瞬间归零。这点比 callback 更容易翻车。


















