设计思想¶
Libuv 的设计思想围绕高效处理 I/O 操作和跨平台兼容性展开,核心目标是提供一个统一的异步 I/O 抽象层,同时保持高性能和可扩展性。
设计架构图¶
Handle 和 Request¶
在 Libuv 中,Handle(句柄) 和 Request(请求) 是两个核心概念,用于管理异步 I/O 操作和资源。它们的设计目的是为了提供一种统一的方式来处理不同类型的 I/O 操作和事件。
Handle(句柄)¶
Handle 是 Libuv 中用于表示长期存在的资源或对象的抽象。它们通常与事件循环相关联,用于监听和处理特定类型的事件。Handle 有下面几个特点:
- 长期存在:Handle 的生命周期通常较长,可能会在整个程序运行期间存在。
- 在
uv_xxx_init
中完成初始化 - 在
uv_xxx_start
中启动 -
在
uv_close
中关闭并释放资源 -
主动监听:Handle 会主动监听某些事件(如 I/O 事件、定时器事件等),并在事件发生时触发回调。
Libuv 提供了多种类型的 Handle,每种 Handle 用于处理特定类型的事件或资源。常见 Handle 类型有:
uv_tcp_t
:用于 TCP 网络通信。uv_udp_t
:用于 UDP 网络通信。uv_pipe_t
:用于进程间通信(IPC)。uv_timer_t
:用于定时器操作。uv_prepare_t
、uv_check_t
、uv_idle_t
:用于事件循环的不同阶段。uv_fs_event_t
:用于监听文件系统事件。uv_signal_t
:用于处理信号事件。
Request(请求)¶
Request 是 Libuv 中用于表示短期操作的抽象。它们通常用于执行一次性的异步操作,并在操作完成后触发回调。
- 短期存在:Request 的生命周期通常较短,操作完成后会被销毁。
- 被动触发:Request 不会主动监听事件,而是用于执行具体的操作(如文件读写、网络请求等)。
Libuv 提供了多种类型的 Request,每种 Request 用于执行特定类型的操作。常见 Request 类型有:
uv_fs_t
:用于文件系统操作(如读写文件)。uv_write_t
:用于写操作(如向 TCP 连接写入数据)。uv_connect_t
:用于连接操作(如 TCP 连接)。uv_work_t
:用于线程池中的任务。uv_getaddrinfo_t
:用于 DNS 查询。
Handle 与 Request 的区别¶
特性 | Handle | Request |
---|---|---|
生命周期 | 长期存在 | 短期存在 |
用途 | 监听事件或管理资源 | 执行一次性操作 |
主动/被动 | 主动监听事件 | 被动执行操作 |
示例 | uv_tcp_t 、uv_timer_t |
uv_fs_t 、uv_write_t |
销毁方式 | 需要显式调用 uv_close |
操作完成后自动销毁 |
使用示例¶
Handle 示例:定时器¶
#include <stdio.h>
#include <uv.h>
void on_timer(uv_timer_t* handle) { printf("Timer triggered!\n"); }
int main() {
uv_loop_t* loop = uv_default_loop();
uv_timer_t timer;
uv_timer_init(loop, &timer); // 初始化 Handle
uv_timer_start(&timer, on_timer, 1000, 2000); // 1秒后启动,每2秒触发一次
uv_run(loop, UV_RUN_DEFAULT); // 运行事件循环
uv_close((uv_handle_t*)&timer, NULL); // 关闭 Handle
return 0;
}
Request 示例:文件读取¶
#include <stdio.h>
#include <stdlib.h>
#include <uv.h>
void on_read(uv_fs_t* req) {
if (req->result < 0) {
fprintf(stderr, "Read error: %s\n", uv_strerror(req->result));
} else {
printf("Data: %s\n", (char*)req->data);
}
uv_fs_req_cleanup(req); // 清理 Request
}
int main() {
uv_loop_t* loop = uv_default_loop();
uv_fs_t read_req;
uv_fs_open(loop, &read_req, "test.txt", O_RDONLY, 0, NULL); // 打开文件
uv_fs_read(loop, &read_req, read_req.result, (uv_buf_t*)malloc(1024), 1, -1,
on_read); // 读取文件
uv_run(loop, UV_RUN_DEFAULT); // 运行事件循环
return 0;
}
事件循环¶
I/O 循环(Event Loop)是 libuv 的核心部分。所有的 I/O 操作都在事件循环当中,事件循环应该被绑定在单个线程上。所以如果想要在多线程上跑的话,应该每个线程都引入事件循环。
基本上各个平台都会支持异步非阻塞的套接字,libuv 会根据平台的不同,去引入对应的最好的机制:Linux 是 epoll、OSX 是 kqueue、Windows 是 IOCP。作为循环的组成部分,网络套接字在空闲时会阻塞住,直到套接字变得可读或者可写,然后事件循环便继续进行。
事件循环核心流程描述如下:
- 更新循环阶段的当前时间。在每次循环的开始阶段,都会缓存当前的时间,接下来循环中都会使用此时间,目的是为了减少系统调用。
- 判断当前循环是否处于活跃状态,如果非活跃状态则直接退出循环。那么,什么情况下循环是活跃状态呢?当循环关联有活跃的 handles 时、或活跃的 requests 时、或存在 closing hanldes 时,循环可被认为是活跃的。
- 执行 timer 阶段。所有早于当前时间的 timers 可被执行,它们关联的回调函数将被调用。
- 执行 pending callbacks 阶段。
- 执行 idle handle 阶段。如果有存在活跃状态的 idle 阶段,它们将在每个循环中被执行。
- 执行 prepare handle 阶段。在循环阻塞 I/O 前,会执行此阶段。
- 计算 poll timeout。在 I/O 阻塞前,计算应该被阻塞多久。有下面的几条规则用于计算 timeout:
- 如果 loop 是 UV_RUN_NOWAIT 方式,则 timeout 为 0
- 如果 loop 将要被停止(调用过 uv_stop()),则 timeout 为 0
- 如果没有活跃的 handles 或 requests,则 timeou 为 0
- 如果存在任一活跃 idle handle,则 timeout 为 0
- 如果存在正在关闭的 handles,则 timeout 为 0
- 上面的情况不满足,取最近的到期 timer 作为 timeout,如果无 timer,则 timeout 为无穷
- 循环阻塞在 I/O。在此阶段,循环一直阻塞,直到上一步计算的 timeout 到达。当有文件描述符变得可读或者可写,将会在此阶段调用它们关联的回调函数。
- 执行 check handle 阶段。
- 调用 close 回调。如果 handle 被调用 uv_close(),则会在此调用回调。
- 如果循环是 UV_RUN_ONCE 方式启动,将会执行 timer 回调。
- 此次循环结束。如果是以 UV_RUN_NOWAIT 或 UV_RUN_ONCE 方式启动的循环,则退出循环。如果是以 UV_RUN_DEFAULT 开始的循环(Node.js 主线程为此状态),如果处于活跃状态则会再次从头进入循环(跳到第一步),否则退出循环。
int uv_run(uv_loop_t* loop, uv_run_mode mode) {
int timeout;
int r;
int ran_pending;
r = uv__loop_alive(loop); // 判断循环是否活跃
if (!r) uv__update_time(loop);
while (r != 0 &&
loop->stop_flag == 0) { // 如果循环活跃且没被手动停止,进入循环
uv__update_time(loop); // 更新当前时间,减少系统调用
uv__run_timers(loop); // 执行 timers 阶段
ran_pending = uv__run_pending(loop); // 执行 pending 阶段
uv__run_idle(loop); // 执行 idle 阶段
uv__run_prepare(loop); // 执行 prepare 阶段
timeout = 0;
if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT)
timeout = uv_backend_timeout(loop); // 计算 timeout
uv__io_poll(loop, timeout); // 进入 io 阻塞阶段,传入上一步计算出的 timeout
uv__metrics_update_idle_time(loop);
uv__run_check(loop); // 执行 check 阶段
uv__run_closing_handles(loop); // 处理 close 相关回调
if (mode == UV_RUN_ONCE) {
uv__update_time(loop);
uv__run_timers(loop);
}
r = uv__loop_alive(loop); // 判断循环是否活跃
if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT)
break; // 如果是 UV_RUN_ONCE 和 UV_RUN_NOWAIT,跳出循环
}
if (loop->stop_flag != 0) loop->stop_flag = 0;
return r;
}