libuv需RAII封装避免未初始化和线程误用:TcpHandle需调uv_tcp_init()并绑定this;uv_read_start()须判UV_EOF/UV_ECONNRESET;work_cb禁调libuv函数;loop退出应以uv_loop_alive()为准。

libuv 本身不提供“高性能”的魔法,它只是把操作系统底层的异步 I/O(epoll、kqueue、IOCP)统一成一套 C 接口。用 C++ 封装它,关键不是堆功能,而是避免踩坑:比如手误调用阻塞函数、忘记 uv_unref() 导致事件循环无法退出、在非主线程直接操作 handle 等。
为什么不能直接 new uv_tcp_t 或 uv_loop_t?
libuv 的 handle 和 loop 都是纯 C 结构体,没有构造/析构语义。C++ 中直接 new uv_tcp_t 只分配内存,但没调用 uv_tcp_init(),该 handle 就是未初始化的野指针状态;同理,uv_loop_t 必须用 uv_loop_init() 初始化,否则后续所有调用都会崩溃或未定义行为。
正确做法是封装 RAII 类:
class TcpHandle {
private:
uv_tcp_t handle_;
uv_loop_t* loop_;
public:
explicit TcpHandle(uv_loopt* loop) : loop(loop) {
uv_tcpinit(loop, &handle);
handle.data = this; // 绑定 C 回调到 this
}
~TcpHandle() {
if (uv_is_closing((uv_handlet*)&handle) == 0) {
uv_close((uv_handlet*)&handle, [](uv_handle_t h) {
delete static_cast>(h->data);
});
}
}
};
uv_read_start() 后必须处理 UV_EOF 和 UV_ECONNRESET
Node.js 能稳定跑十年,很大一部分功劳是它对各种断连错误做了穷举式处理。libuv 的 uv_read_start() 回调中,uv_buf_t 的 len 字段为 0 并不意味着连接关闭——真正信号是 nread == UV_EOF 或 nread 。
立即学习“C++免费学习笔记(深入)”;
常见漏判场景:
-
nread == UV_ECONNRESET:对方 RST 断连,需立即uv_close() -
nread == UV_EOF:对方正常close(),可做 graceful shutdown nread :其他错误(如UV_ENOTCONN),一般也应 close
uv_queue_work() 不等于线程池,别在 work_cb 里调 libuv 函数
uv_queue_work() 的 work_cb 运行在线程池中,此时 **不能调用任何 libuv 函数**(如 uv_write()、uv_timer_start()),因为这些函数要求运行在 loop 所属线程。所有 I/O 操作必须收束到 after_work_cb,它由 event loop 在主线程调用。
典型错误写法:
// ❌ 错误:在 work_cb 中调用 uv_write()
void work_cb(uv_work_t* req) {
// ... 耗时计算
uv_write(&write_req, (uv_stream_t*)&client, &buf, 1, write_cb); // crash!
}正确方式是把结果传给 after_work_cb,再在那里发包:
// ✅ 正确:只在 after_work_cb 中操作 handle
void after_work_cb(uv_work_t* req, int status) {
auto* data = static_cast(req->data);
uv_buf_t buf = uv_buf_init(data->result.c_str(), data->result.size());
uv_write(&data->write_req, (uv_stream_t*)&data->client, &buf, 1, write_cb);
} uv_run() 返回 false 不代表没任务,要看 uv_loop_alive()
很多人用 uv_run(loop, UV_RUN_ONCE) 做轮询,然后判断返回值决定是否继续。但这是错的:uv_run() 返回 0(false)只表示本次没执行任何回调,不代表 loop 已空闲——可能还有 pending 的 timer、prepare、check handle 没触发,或者刚被 uv_ref() 过。
判断 loop 是否该退出,唯一可靠方式是 uv_loop_alive(loop) == 0。这也是 Node.js 内部实际用的逻辑。
尤其注意:uv_timer_start() 默认会 ref loop,而 uv_timer_stop() 不自动 unref;如果你手动 uv_ref() 了某个 handle,就必须配对 uv_unref(),否则 loop 永远不会退出。











