协程中不能直接co_await裸socket,需封装为awaitable对象;推荐用boost::asio::use_awaitable;TLS握手需注意资源生命周期、串行操作保护及细粒度控制。

协程改写异步握手时,co_await 不能直接套在裸 socket 上
裸 socket 是阻塞或非阻塞的系统资源,不满足 awaitable 要求。强行 co_await 一个 recv() 调用会编译失败,报错类似 no operator "co_await" matches these operands。
必须封装成可等待对象:要么用第三方库(如 libunifex、cppcoro)提供的 async_read / async_write,要么自己实现 await_suspend/await_resume —— 但后者需配合 epoll/kqueue 或 IOCP,不是加个 co_await 就完事。
- 推荐路径:用
boost::asio::use_awaitable,它已封装好async_handshake、async_read等成员函数,返回原生awaitable - 别踩坑:别试图对
std::thread或std::mutex做co_await—— 它们不参与调度,会卡死或 UB - 注意:
boost::asio::io_context必须在协程挂起前已启动(run()或run_one()),否则协程永远不恢复
TLS 握手阶段拆分协程,async_handshake 的 handshake_type 参数不能忽略
典型 TLS 握手包含 client_hello → server_hello → cert → finished 多轮交互,但 boost::asio::ssl::stream::async_handshake 默认只暴露一个顶层 awaitable。如果协议要求在收到 server_cert 后插入自定义校验逻辑(比如查本地策略库),就不能全交给它自动完成。
此时得降级到更细粒度操作:async_handshake 支持传入 boost::asio::ssl::stream_base::server 或 client,但关键在后续手动调用 async_read 和 async_write 配合 ssl::stream::next_layer()。
立即学习“C++免费学习笔记(深入)”;
- 常见错误:把
async_handshake(client)当成原子操作,结果证书校验失败后无法中断流程,只能等超时 - 正确做法:用
ssl::stream::async_handshake(boost::asio::ssl::stream_base::client, token)启动,再通过ssl::stream::lowest_layer().async_read_some()拦截原始 TLS 记录,解析Certificate消息 - 性能影响:手动解析 TLS 记录会绕过 asio ssl 内部缓冲,增加拷贝和 CPU 开销;若无强定制需求,优先用
set_verify_callback注入校验逻辑
多个握手步骤串行 await,别让 co_await 变成隐式同步点
看似自然的写法:co_await step1(); co_await step2(); co_await step3();,在握手流程里容易误以为“并发安全”,其实每个 co_await 都会让出执行权,但底层 socket 仍是单线程共享的 —— 如果多个协程同时读写同一个 ssl::stream,会触发未定义行为(asio 文档明确要求:one stream, one operation at a time)。
- 典型现象:随机出现
SSL_ERROR_SYSCALL或asio.ssl.stream: bad length错误,尤其在高并发重连场景 - 必须加保护:用
boost::asio::strand包裹整个握手协程,或用awaitable_mutex(cppcoro)限制同一时刻仅一个协程访问该连接 - 别省事:不要用
std::shared_mutex手动锁 —— 协程挂起期间锁不会释放,会导致死锁
协程栈与 TLS 上下文生命周期必须对齐
握手过程中创建的临时对象(如证书解析结果、密钥派生中间值)如果放在协程栈上,而协程被挂起后栈帧销毁,resume 时访问就是野指针。更隐蔽的是 ssl::context 实例若被提前析构,后续 async_handshake 会 crash 在 SSL_CTX_free 相关断言上。
这不是语法问题,是 RAII 和协程生命周期的错位。
- 容易忽略:把
ssl::context声明在协程函数内部,而不是传入或由连接对象持有 - 正确方式:所有 SSL 相关资源(
ssl::context、ssl::stream、证书缓存)必须比协程活得久;建议绑定到 connection 对象的成员变量 - 调试提示:启用 asio 的
ASIO_ENABLE_HANDLER_TRACKING,能快速定位 resume 时访问已释放内存的协程栈
真正麻烦的从来不是怎么写 co_await,而是搞清谁 owns 什么、谁负责 cleanup、以及挂起那一刻哪些指针还有效。










