跳转至

设计思想

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_tuv_check_tuv_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_tuv_timer_t uv_fs_tuv_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。作为循环的组成部分,网络套接字在空闲时会阻塞住,直到套接字变得可读或者可写,然后事件循环便继续进行。

事件循环核心流程描述如下:

  1. 更新循环阶段的当前时间。在每次循环的开始阶段,都会缓存当前的时间,接下来循环中都会使用此时间,目的是为了减少系统调用。
  2. 判断当前循环是否处于活跃状态,如果非活跃状态则直接退出循环。那么,什么情况下循环是活跃状态呢?当循环关联有活跃的 handles 时、或活跃的 requests 时、或存在 closing hanldes 时,循环可被认为是活跃的。
  3. 执行 timer 阶段。所有早于当前时间的 timers 可被执行,它们关联的回调函数将被调用。
  4. 执行 pending callbacks 阶段。
  5. 执行 idle handle 阶段。如果有存在活跃状态的 idle 阶段,它们将在每个循环中被执行。
  6. 执行 prepare handle 阶段。在循环阻塞 I/O 前,会执行此阶段。
  7. 计算 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 为无穷
  8. 循环阻塞在 I/O。在此阶段,循环一直阻塞,直到上一步计算的 timeout 到达。当有文件描述符变得可读或者可写,将会在此阶段调用它们关联的回调函数。
  9. 执行 check handle 阶段。
  10. 调用 close 回调。如果 handle 被调用 uv_close(),则会在此调用回调。
  11. 如果循环是 UV_RUN_ONCE 方式启动,将会执行 timer 回调。
  12. 此次循环结束。如果是以 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;
}