2.libuv基础
libuv基础:
Libuv强制采用异步的、事件驱动的编程风格。它的核心工作是提供一个基于I/O和其他活动通知的事件循环和回调。Libuv提供了核心实用程序,如计时器、非阻塞网络支持、异步文件系统访问、子进程等。
事件循环:
在事件驱动编程中,应用程序对某些事件感兴趣,并在事件发生时对其作出响应。libuv负责从操作系统收集事件或监视其他事件来源,用户可以注册回调,以便在事件发生时调用。事件循环通常会永远运行下去。在伪代码:
1 while there are still events to process: 2 e = get the next event 3 if there is a callback associated with e: 4 call the callback
以下是一些事件的例子:
- 文件已准备好写入
- 套接字有可以读取的数据
- 计时器超时
这个事件循环被uv_run()封装——使用libuv时的end-all函数。
系统程序最常见的活动是处理输入和输出,而不是大量的数字运算。使用传统输入/输出函数(read, fprintf等)的问题是它们是阻塞的。与处理器的速度相比,实际写入硬盘或从网络读取所需的时间长得不成比例。函数直到任务完成后才返回,因此您的程序什么也不做。对于要求高性能的程序,这是一个主要的障碍,因为其他活动和其他I/O操作一直在等待。
标准的解决方案之一是使用线程。每个阻塞I/O操作都在单独的线程(或线程池)中启动。当阻塞函数在线程中被调用时,处理器可以调度另一个真正上需要CPU的线程运行。
libuv所遵循的方法使用了另一种风格,即异步、非阻塞风格。大多数现代操作系统都提供事件通知子系统。例如,对套接字的一个普通的read调用会阻塞,直到发送方实际发送了一些东西。相反,应用程序可以请求操作系统监视套接字并在队列中放入事件通知。应用程序可以方便地检查事件(可能在最大限度使用处理器之前做一些数字运算)并获取数据。它是异步的,因为应用程序在某一点表示兴趣,然后在另一点(在时间和空间上)使用数据。它是非阻塞的,因为应用程序进程可以自由地执行其他任务。这很符合libuv的事件循环方法,因为操作系统事件可以被视为另一个libuv事件。非阻塞确保其他事件可以像在[1]中一样快速地被处理。
- 注释:I / O是如何在后台运行并不是我们关心的,但由于我们的计算机硬件的工作方式,与线程处理器的基本单位,libuv和操作系统通常会运行背景/工作线程和/或轮询以非阻塞的方式执行任务。
Bert Belder, libuv核心开发者之一,有一个小视频解释libuv的架构和它的背景。如果你之前没有libuv或libev的经验,这是一个快速,有用的视频。
Hello World:
了解了这些基本知识之后,让我们编写第一个libuv程序。它什么也不做,除了启动一个将立即退出的循环。
helloworld/main.c
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <uv.h> 4 5 int main() { 6 uv_loop_t *loop = malloc(sizeof(uv_loop_t)); 7 uv_loop_init(loop); 8 9 printf("Now quitting.\n"); 10 uv_run(loop, UV_RUN_DEFAULT); 11 12 uv_loop_close(loop); 13 free(loop); 14 return 0; 15 }
此程序立即退出,因为它没有事件要处理。必须告诉libuv事件循环注意使用各种API函数的事件。
从libuv v1.0开始,用户应该在使用uv_loop_init(uv_loop_t *)初始化之前为循环分配内存。这允许您插入自定义内存管理。记住使用uv_loop_close(uv_loop_t *)来反初始化循环,然后删除存储。这些例子从来没有关闭循环,因为在循环结束后程序将退出,系统将回收内存。
生产级项目,特别是长时间运行的系统程序,应该注意正确发布。
默认循环:
默认循环由libuv提供,可以使用uv_default_loop()访问。如果你只想要一个循环,你应该使用这个循环。
- 注意:Node.js使用默认循环作为其主循环。如果您正在编写绑定,则应该注意这一点。
错误处理:
可能失败的初始化函数或同步函数在出错时返回一个负数。可能失败的异步函数将向它们的回调函数传递一个状态参数。错误消息被定义为UV_E*常量。
可以使用uv_strerror(int)和uv_err_name(int)函数分别获取描述错误或错误名称的const char *。
I/O读回调(例如文件和套接字)被传递一个参数nread。如果nread小于0,则有一个错误(UV_EOF是文件结束错误,您可能希望以不同的方式处理)。
处理和请求:
Libuv由对特定事件感兴趣的用户工作。这通常是通过创建一个I/O设备、定时器或进程的句柄来完成的。句柄是不透明的结构体,命名为uv_TYPE_t,其中type表示句柄的用途。
libuv 监视器:
1 /* Handle types. */ 2 typedef struct uv_loop_s uv_loop_t; 3 typedef struct uv_handle_s uv_handle_t; 4 typedef struct uv_dir_s uv_dir_t; 5 typedef struct uv_stream_s uv_stream_t; 6 typedef struct uv_tcp_s uv_tcp_t; 7 typedef struct uv_udp_s uv_udp_t; 8 typedef struct uv_pipe_s uv_pipe_t; 9 typedef struct uv_tty_s uv_tty_t; 10 typedef struct uv_poll_s uv_poll_t; 11 typedef struct uv_timer_s uv_timer_t; 12 typedef struct uv_prepare_s uv_prepare_t; 13 typedef struct uv_check_s uv_check_t; 14 typedef struct uv_idle_s uv_idle_t; 15 typedef struct uv_async_s uv_async_t; 16 typedef struct uv_process_s uv_process_t; 17 typedef struct uv_fs_event_s uv_fs_event_t; 18 typedef struct uv_fs_poll_s uv_fs_poll_t; 19 typedef struct uv_signal_s uv_signal_t; 20 21 /* Request types. */ 22 typedef struct uv_req_s uv_req_t; 23 typedef struct uv_getaddrinfo_s uv_getaddrinfo_t; 24 typedef struct uv_getnameinfo_s uv_getnameinfo_t; 25 typedef struct uv_shutdown_s uv_shutdown_t; 26 typedef struct uv_write_s uv_write_t; 27 typedef struct uv_connect_s uv_connect_t; 28 typedef struct uv_udp_send_s uv_udp_send_t; 29 typedef struct uv_fs_s uv_fs_t; 30 typedef struct uv_work_s uv_work_t;
句柄代表长生命周期的对象。此类句柄上的异步操作是使用请求标识的。请求是短暂的(通常只在一个回调中使用),并且通常表示句柄上的一个I/O操作。请求用于保存单个操作的发起和回调之间的上下文。例如,UDP套接字由uv_udp_t表示,而对套接字的单独写入使用uv_udp_send_t结构,该结构在写入完成后传递给回调。
句柄应该设置匹配的:
uv_TYPE_init(uv_loop_t *, uv_TYPE_t *)
函数
回调是当观察者感兴趣的事件发生时,libuv就会调用的函数。特定于应用程序的逻辑通常在回调中实现。例如,IO观察器的回调将接收从文件读取的数据,计时器回调将在超时时触发,等等。
空转
下面是一个使用空转句柄的例子。callback在事件循环的每一个回合中被调用一次。在实用程序中讨论了空闲句柄的用例。让我们使用一个空闲的监控器来查看监控器的生命周期,并看看uv_run()现在是如何阻塞的,因为存在一个监控器。空闲监视程序在达到计数时停止,uv_run()退出,因为没有活动的事件监视程序。
idle-basic/main.c
1 #include <stdio.h> 2 #include <uv.h> 3 4 int64_t counter = 0; 5 6 void wait_for_a_while(uv_idle_t* handle) { 7 counter++; 8 9 if (counter >= 10e6) 10 uv_idle_stop(handle); 11 } 12 13 int main() { 14 uv_idle_t idler; 15 16 uv_idle_init(uv_default_loop(), &idler); 17 uv_idle_start(&idler, wait_for_a_while); 18 19 printf("Idling...\n"); 20 uv_run(uv_default_loop(), UV_RUN_DEFAULT); 21 22 uv_loop_close(uv_default_loop()); 23 return 0; 24 }
存储环境
在基于回调的编程风格中,你通常希望在调用站点和回调之间传递一些“上下文”——应用程序特定的信息。所有的句柄和请求都有一个void*数据成员,你可以将其设置为上下文,并在回调中进行强制转换。这是整个C库生态系统中使用的一种常见模式。此外,uv_loop_t也有一个类似的数据成员。

浙公网安备 33010602011771号