libuv概览

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


本人享有博客文章的版权,转载请标明出处 http://blog.csdn.net/baidu20008

你可能感兴趣的:(libuv中文编程指南,libuv介绍)