0%

libuv简介

备忘。

1. 错误处理

一个库,让用户知道内部错误,一般就是返回错误码,直接从函数返回;或者用一个错误接口统一起来,在函数调用后告诉用户(类似openssl)。libuv用的是前一种。

关于错误码libuv提供有几个常用的接口,自己写库的时候也可以参考。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 错误码--->错误描述字符串
const char* uv_strerror(int err);

// 错误码--->错误描述符字符串,返回给用户提供的内存中
char* uv_strerror_r(int err, char* buf, size_t buflen);

// 错误码--->字符描述的错误码
const char* uv_err_name(int err);

// 错误码--->字符描述的错误码,返回给用户提供的内存中
char* uv_err_name_r(int err, char* buf, size_t buflen);

// 将系统上的错误码转换成libuv中的错误码,
int uv_translate_sys_error(int sys_errno);

2. 版本号的处理

libuv提供有几个宏,用于在编译时候区分版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
UV_VERSION_MAJOR
UV_VERSION_MINOR
UV_VERSION_PATCH

UV_VERSION_IS_RELEASE
UV_VERSION_SUFFIX

// 一个版本号的整数,这样算出来的
// #define UV_VERSION_HEX ((UV_VERSION_MAJOR << 16) | \
// (UV_VERSION_MINOR << 8) | \
// (UV_VERSION_PATCH))
// 比如1.2.3就是 0x010203,当然1.11.3就是 0x010b03 了
UV_VERSION_HEX

还有两个用于在运行时区分版本的函数:

1
2
3
4
5
// 返回 UV_VERSION_HEX
unsigned int uv_version(void);

// 返回一个版本号的字符串
const char* uv_version_string(void);

编译时和运行时都需要,所以要给用户提供宏和函数两种形式的版本号。

3. libuv中的对象

libuv中组对象的方式很有意思,比如说 uv_handle_t 这样定义:

1
2
3
4
5
typedef struct uv_handle_s uv_handle_t;

struct uv_handle_s {
UV_HANDLE_FIELDS
};

然后uv_stream_t是这样定义的:

1
2
3
4
5
6
typedef struct uv_stream_s uv_stream_t;

struct uv_stream_s {
UV_HANDLE_FIELDS
UV_STREAM_FIELDS
};

另外还有 uv_tcp_t:

1
2
3
4
5
6
7
typedef struct uv_tcp_s uv_tcp_t;

struct uv_tcp_s {
UV_HANDLE_FIELDS
UV_STREAM_FIELDS
UV_TCP_PRIVATE_FIELDS
};

可以看到 uv_handle_t, uv_stream_t, uv_tcp_t 第一个都是 UV_HANDLE_FIELDS; uv_stream_t, uv_tcp_t第二个都是 UV_STREAM_FIELDS,这样就可以把 uv_handle_t当成父类,uv_stream_t, uv_tcp_t当成子类和孙子——当然要用到c的强制类型转换。这其实不算是个很好的用法,但好在简单。

有些对象中的字段可以给用户使用,但其实所有字段对用户都是公开的,这就需要做好注释,没有办法用编译器去约束。

libuv中主要对象有以下这些:

  1. uv_loop_t: libuv的事件驱动对象,也就是主循环在这上边挂着,关于事件相关的上下文都在这里;
  2. uv_handle_t:表示一个基本对象,这个相当于父类,下边还有子类;
  3. uv_req_t: 一个连接上的具体请求,比如说在tcp上调用 int uv_write(uv_write_t* req, uv_stream_t* handle, const uv_buf_t bufs[], unsigned int nbufs, uv_write_cb cb) 进行一次写操作, 这个操作会立马返回,写成功之后的结果会在 void (*uv_write_cb)(uv_write_t* req, int status) 中返回给用户,这里 uv_req_t 相当于在 uv_write()uv_write_cb() 之间传递libuv和用户自己的一些数据的。可以看到这也是个父类;

uv_handle_t 下又分:

  1. uv_timer_t: 定时器;
  2. uv_prepare_t: 每次轮训io之前,会调用这个事件句柄一次;
  3. uv_check_t: 每次轮训io之后,会调用这个;
  4. uv_idle_t: 每次循环在 uv_prepare_t 之前会调用这个,跟 uv_prepare_t 不同的是,如果有 uv_idle_t 句柄,epoll 会立马返回,不会阻塞(超时等待);
  5. uv_async_t: 提供一个异步通知句柄,可以将当前任务放到额外的线程去做,然后用该句柄通知主循环最后的结果。有 eventfd 就用 eventfd 实现,没有就用 pipe 实现;
  6. uv_poll_t: 有些第三方库依赖socket事件通知进行驱动,该句柄可以将这些句柄接入libuv的主循环去监听读写和连接的事件;
  7. uv_signal_t: 信号封装;
  8. uv_process_t: 进程封装;
  9. uv_stream_t: 双工连接句柄。有三个子类: uv_tcp_t, uv_pipe_t, uv_tty_t。还有几个相关的 uv_req_t 子类: uv_connect_t, uv_shutdown_t, uv_write_t;
  10. uv_udp_t: 封装UDP通信句柄,还有个相关的 req 子类: uv_udp_send_t;
  11. uv_fs_event_t: 可以监听一个文件路径的变化,比如文件重命名等。
  12. uv_fs_poll_t: 跟 uv_fs_event_t 类似,但使用 state 去监听路径变化。

uv_req_t 下又分:

  1. uv_connect_t: uv_stream_t 建立连接的请求
  2. uv_shutdown_t: uv_stream_t 关闭连接
  3. uv_write_t: uv_stream_t 写请求
  4. uv_udp_send_t: uv_udp_t 写请求
  5. uv_fs_t: 文件异步操作请求
  6. uv_work_t: 线程池异步任务请求
  7. uv_getaddrinfo_tuv_getnameinfo_t: 对DNS的异步请求
  8. uv_random_t: 生成随机数的请求

4. 事件驱动的一个常规流程

就是这个图了
事件驱动流程

参考:
http://docs.libuv.org/en/v1.x/index.html