基本的都在api文档里说明了,这里挑几个做备忘。
1. 错误处理
一个库,让用户知道内部错误,一般就是返回错误码,直接从函数返回;或者用一个错误接口统一起来,在函数调用后告诉用户(类似openssl)。libuv用的是前一种。
关于错误码libuv提供有几个常用的接口,自己写库的时候也可以参考。
// 错误码--->错误描述字符串
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提供有几个宏,用于在编译时候区分版本:
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
还有两个用于在运行时区分版本的函数:
// 返回 UV_VERSION_HEX
unsigned int uv_version(void);
// 返回一个版本号的字符串
const char* uv_version_string(void);
编译时和运行时都需要,所以要给用户提供宏和函数两种形式的版本号。
3. libuv中的对象
libuv中组对象的方式很有意思,比如说 uv_handle_t
这样定义:
typedef struct uv_handle_s uv_handle_t;
struct uv_handle_s {
UV_HANDLE_FIELDS
};
然后uv_stream_t
是这样定义的:
typedef struct uv_stream_s uv_stream_t;
struct uv_stream_s {
UV_HANDLE_FIELDS
UV_STREAM_FIELDS
};
另外还有 uv_tcp_t
:
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中主要对象有以下这些:
-
uv_loop_t
: libuv的事件驱动对象,也就是主循环在这上边挂着,关于事件相关的上下文都在这里; -
uv_handle_t
:表示一个双工连接对象,比如一个tcp连接,这个相当于父类,下边还有子类; -
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
下又分:
-
uv_timer_t
: 定时器; -
uv_prepare_t
: 每次轮训io之前,会调用这个事件句柄一次; -
uv_check_t
: 每次轮训io之后,会调用这个; -
uv_idle_t
: 每次循环在uv_prepare_t
之前会调用这个,跟uv_prepare_t
不同的是,如果有uv_idle_t
句柄,epoll
会立马返回,不会阻塞(超时等待); -
uv_async_t
: 提供一个异步通知句柄,可以将当前任务放到额外的线程去做,然后用该句柄通知主循环最后的结果。有eventfd
就用eventfd
实现,没有就用pipe
实现; -
uv_poll_t
: 有些第三方库依赖socket事件通知进行驱动,该句柄可以将这些句柄接入libuv的主循环去监听读写和连接的事件; -
uv_signal_t
: 信号封装; -
uv_process_t
: 进程封装; -
uv_stream_t
: 双工连接句柄。有三个子类:uv_tcp_t, uv_pipe_t, uv_tty_t
。还有几个相关的uv_req_t
子类:uv_connect_t, uv_shutdown_t, uv_write_t
; -
uv_udp_t
: 封装UDP通信句柄,还有个相关的req
子类:uv_udp_send_t
; -
uv_fs_event_t
: 可以监听一个文件路径的变化,比如文件重命名等。 -
uv_fs_poll_t
: 跟uv_fs_event_t
类似,但使用state
去监听路径变化。
uv_req_t
下又分:
-
uv_connect_t
:uv_stream_t
建立连接的请求 -
uv_shutdown_t
:uv_stream_t
关闭连接 -
uv_write_t
:uv_stream_t
写请求 -
uv_udp_send_t
:uv_udp_t
写请求 -
uv_fs_t
: 文件异步操作请求 -
uv_work_t
: 线程池异步任务请求 -
uv_getaddrinfo_t
和uv_getnameinfo_t
: 对DNS的异步请求 -
uv_random_t
: 生成随机数的请求
4. 事件驱动的一个常规流程
就是这个图了
参考:
http://docs.libuv.org/en/v1.x/index.html