libuv 的API是C风格的,很容易读。你可能觉得uv.h中暴露了太多的数据结构了,不够简洁,我想是因为libuv涵盖的内容非常的广泛,从网络,pipe,文件,终端等等,包罗万象。而且uv.h也不是接口的全部,还有两个头文件,uv-unix.h和uv-win.h,里面定义了操作系统specific的数据结构。其实,libuv已经对接口的简化做了一些努力,比如说,通过uv_write一个函数,我们可以写TCP,Pipe和tty。
因为libuv涵盖广泛,我们的目的只是为了了解它如何与nodejs,v8协调工作的,不会面面俱到。有几个重要的数据结构有必要先了解一下:
#define UV_HANDLE_FIELDS \ uv_loop_t* loop; uv_handle_type type; uv_close_cb close_cb; void* data; UV_HANDLE_PRIVATE_FIELDS /* The abstract base class of allhandles. */ struct uv_handle_s { UV_HANDLE_FIELDS };
uv_handle_s是其它handle的父类,比如说,uv_tcp_s就是它的子类,在那里你能找到socket。loop字段表明它属于哪个loop,handle里还有一些callback函数,异步调用通常会有两个参数,一个是handle,一个是callback函数,调用完成的时候,libuv会调用callback函数。data字段是留给使用者的,nodejs实现异步机制的时候会用到。
#define UV_REQ_FIELDS \ uv_req_type type; void* data; UV_REQ_PRIVATE_FIELDS /* Abstract base class of all requests. */ struct uv_req_s { UV_REQ_FIELDS };
这是所有request的父类。loop维护一个request queue。data字段有时用来存放handle。
再来看loop,这是libuv里面最关键的数据结构了,一般会指定一个线程负责一个loop的处理,nodejs只使用了一个loop,由主线程负责对它进行处理。
struct uv_loop_s { UV_LOOP_PRIVATE_FIELDS uv_ares_task_t* uv_ares_handles_; uv_async_t uv_eio_want_poll_notifier; uv_async_t uv_eio_done_poll_notifier; uv_idle_t uv_eio_poller; uv_counters_t counters; uv_err_t last_err; void* data; };
我们只看Windows平台(参看uv-win.h)
#define UV_LOOP_PRIVATE_FIELDS \ HANDLE iocp; int refs; int64_t time; uv_req_t* pending_reqs_tail; uv_handle_t* endgame_handles;
我们已经知道有一些handles会和loop关联在一起,字段refs就是和它相关的handle的数量。它还会有一个请求队列,pending_reqs_tail。对windows来说,它有一个io completion port。初始化loop的时候会创建一个completion port,之后其它handles可以加入,通过这个port来监控事件。比如,当有新的socket建立的时候,再调用一次CreateIOCompletionPort()可以使用这个port来监控socket事件。
了解了数据结构以后,程序怎么运行应该就大致有数了。来看一下执行过程。
#define UV_LOOP_ONCE(loop, poll) \ do{ uv_update_time((loop)); uv_process_timers((loop)); /* Call idle callbacks if nothing to do. */ if ((loop)->pending_reqs_tail == NULL && (loop)->endgame_handles == NULL) { uv_idle_invoke((loop)); } uv_process_reqs((loop)); uv_process_endgames((loop)); if((loop)->refs <= 0) { break; } uv_prepare_invoke((loop)); poll((loop), (loop)->idle_handles == NULL && (loop)->pending_reqs_tail == NULL && (loop)->endgame_handles == NULL && (loop)->refs > 0); uv_check_invoke((loop)); } while (0); #define UV_LOOP(loop, poll) while ((loop)->refs > 0) { UV_LOOP_ONCE(loop, poll) }
函数poll()检查completion port,有事件发生的时候,产生一条request,插入到请求队列中。
函数uv_process_reqs()从消息队列中取出请求,处理请求。此函数在处理请求的过程中可能会调用用户传入的callback。
函数uv_process_endgames()清理已经关闭的handle,同时减掉refs。
首先得明白如下几点:
uv_run封装了事件驱动编程模型的while(1)循环。
uv_default_loop为libuv默认的事件循环
libuv 通过监视器(Watcher)来对特定事件进行监控, 监视器通常是类似uv_TYPE_t 结构体的封装, TYPE 代表该监视器的用途, libuv 所有的监视器类型如下:
typedefstruct uv_loop_s uv_loop_t;
typedefstruct uv_err_s uv_err_t;
typedefstruct uv_handle_s uv_handle_t;
typedefstruct uv_stream_s uv_stream_t;
typedefstruct uv_tcp_s uv_tcp_t;
typedefstruct uv_udp_s uv_udp_t;
typedefstruct uv_pipe_s uv_pipe_t;
typedefstruct uv_tty_s uv_tty_t;
typedefstruct uv_poll_s uv_poll_t;
typedefstruct uv_timer_s uv_timer_t;
typedefstruct uv_prepare_s uv_prepare_t;
typedefstruct uv_check_s uv_check_t;
typedefstruct uv_idle_s uv_idle_t;
typedefstruct uv_async_s uv_async_t;
typedefstruct uv_process_s uv_process_t;
typedefstruct uv_fs_event_s uv_fs_event_t;
typedefstruct uv_fs_poll_s uv_fs_poll_t;
typedefstruct uv_signal_s uv_signal_t;
可以看到有各种不同类型事件的监听。tcp udp pipe stream handle process等等。
应该要知道的是所有监视器都是uv_handle_t的“子类”,在libuv中被称为句柄。
既然实现语言都是c了,那么实际上也不存在“子类”这个说法了吧。不过是,除uv_handle_t外,其他的句柄都完整的包含了uv_handle_t的成员。
uv_TYPE_init(uv_TYPE_t*)
ps:某些监视器初始化函数的第一个参数为事件循环的句柄,比如tcp。
uv_TYPE_start(uv_TYPE_t*, callback) //开始监听
uv_TYPE_stop(uv_TYPE_t*) //停止监听
空转idling
空转监视器的回调函数会被不断的重复调用。
文件系统中提供了一些和文件相关的异步非阻塞操作。简单的文件读写是通过 uv_fs_* 函数族和与之相关的 uv_fs_t 结构体完成的。
网络提供了非常便捷的创建tcp udp连接的接口。
线程同样提供了便捷的接口去创建线程等。
调用某一个事件循环会把uv_run的程序阻塞。
总的来讲,libuv代码写得还是比较容易懂的。代码广泛地运用了宏定义技巧,本人代码看的少,还是能够从代码中学到不少知识的。
libuv编程指南:
http://www.256code.com/uvbook/
libuv和nodejs介绍:
https://cnodejs.org/topic/4f571a16a680d212781ccf9f