libuv是一个高性能事件驱动库,是node.js 的底层实现。libuv严格使用异步、事件驱动的编程风格。其核心工作是提供事件循环及基于 I/O或其他活动事件的回调机制。libuv库包含了诸如计时器、非阻塞网络支持、异步文件系统访问、子进程等核心工具。在事件驱动程序设计中,应用程序关注特定事件并在它们发生时作出响应。libuv负责从操作系统那里收集事件或监视其他资源的事件,而用户可以注册在某个事件发生时要调用的回调函数。事件循环通常会不间断运行。
监视器是libuv用户用于监视特定事件的工具。即,使用libuv的应用通过特定的监视器watcher让libuv通知应用那些应用想要知道发生的事件。大致的过程是,应用创建监视器,在监视器上注册事件及其回调函数,当已注册的事件发生时libuv触发注册的回调函数。
监视器类型
我们从uv.h头文件中任意选取一个监视器定义一探究竟。就挑常在例程中出现的uv_tcp_t开始。
typedef struct uv_tcp_s uv_tcp_t;
/*
* uv_tcp_t is a subclass of uv_stream_t
*
* Represents a TCP stream or TCP server.
*/
struct uv_tcp_s {
UV_HANDLE_FIELDS
UV_STREAM_FIELDS
UV_TCP_PRIVATE_FIELDS
};
uv_tcp_t只是一个别名,指向的是uv_tcp_s结构体。注释中指出uv_tcp_s是uv_stream_t的子类。我们明显看到这就是一个简单的结构体定义,未继承自任何其他的类或结构体。不知注释中的“子类”一说如何解释。既然提到了uv_stream_t,我们也看看它的定义。
typedef struct uv_stream_s uv_stream_t;
/*
* uv_stream_t is a subclass of uv_handle_t
*
* uv_stream is an abstract class.
*
* uv_stream_t is the parent class of uv_tcp_t, uv_pipe_t, uv_tty_t, and
* soon uv_file_t.
*/
struct uv_stream_s {
UV_HANDLE_FIELDS
UV_STREAM_FIELDS
};
果不其然,uv_stream_t是uv_stream_s结构体的别名。注释中指出,uv_stream_s是uv_handle_t的子类,是个抽象类。同时还指出,uv_stream_s是uv_tcp_t、uv_pipe_t、uv_tty_t和uv_file_t的父类。这里同样使用了“子类”,但依然看不到任何继承声明。接着看uv_handle_t。是uv_handle_s结构体的别名。
typedef struct uv_handle_s uv_handle_t;
uv_handle_s结构体定义如下。注释中说明这是所有句柄的抽象基类。到现在为止,我们还不确定这里的“所有句柄”是不是就是指所有的监视器。
/* The abstract base class of all handles. */
struct uv_handle_s {
UV_HANDLE_FIELDS
};
通过查看uv_tcp_t结构及分析相关联结构,从注释中发现libuv中的子类父类概念与C++面向对象思想中的继承关系不一致。通过分析uv_handle_s和uv_stream_s两个类可以了解到libuv中使用的继承概念。
/* The abstract base class of all handles. */
struct uv_handle_s {
UV_HANDLE_FIELDS
};
/*
* uv_stream_t is a subclass of uv_handle_t
*
* uv_stream is an abstract class.
*
* uv_stream_t is the parent class of uv_tcp_t, uv_pipe_t, uv_tty_t, and
* soon uv_file_t.
*/
struct uv_stream_s {
UV_HANDLE_FIELDS
UV_STREAM_FIELDS
};
注释中已经说明了,uv_stream_s是uv_handle_s的子类。二者声明的重要不同在于内部成员的不同。uv_stream_s结构体只比uv_handle_s结构体多了UV_STREAM_FIELDS。他们都有共同的部分:UV_HANDLE_FIELDS。从C结构体的内存布局来看,uv_handle_s和uv_streams_s在结构体的前部是完全相同的。现在可以猜测libuv中的继承概念不同于面向对象中的继承概念。libuv并不通过编程语言自身提供的继承机制来达到继承目的。而是通过C语言结构体内存布局的特性来达到类似的目的。通过以上分析,我们猜测uv_tcp_s既然是uv_stream_s的子类。按照libuv的继承概念,uv_tcp_s的内部布局应该比uv_stream_s在尾部多了点内容。查看uv_tcp_s的声明确实如此。
struct uv_tcp_s {
UV_HANDLE_FIELDS
UV_STREAM_FIELDS
UV_TCP_PRIVATE_FIELDS
};
/* Handle types. */ typedef struct uv_loop_s uv_loop_t; typedef struct uv_handle_s uv_handle_t; typedef struct uv_stream_s uv_stream_t; typedef struct uv_tcp_s uv_tcp_t; typedef struct uv_udp_s uv_udp_t; typedef struct uv_pipe_s uv_pipe_t; typedef struct uv_tty_s uv_tty_t; typedef struct uv_poll_s uv_poll_t; typedef struct uv_timer_s uv_timer_t; typedef struct uv_prepare_s uv_prepare_t; typedef struct uv_check_s uv_check_t; typedef struct uv_idle_s uv_idle_t; typedef struct uv_async_s uv_async_t; typedef struct uv_process_s uv_process_t; typedef struct uv_fs_event_s uv_fs_event_t; typedef struct uv_fs_poll_s uv_fs_poll_t; typedef struct uv_signal_s uv_signal_t;如果用libuv写过一些例子的话,应该知道这里的大部分uv_TYPE_t都是监视器类型。我挑选了uv_loop_t类型看看它是否也是uv_handle_t的“子类”。
struct uv_loop_s { /* User data - use this for whatever. */ void* data; /* Loop reference counting */ unsigned int active_handles; void* handle_queue[2]; void* active_reqs[2]; /* Internal flag to signal loop stop */ unsigned int stop_flag; UV_LOOP_PRIVATE_FIELDS };从uv_loop_s的声明可以看出,它应该不是uv_handle_t的子类。只能说是看上去,因为uv_handle_s的声明中是一个宏:UV_HANDLE_FIELDS。宏UV_HANDLE_FIELDS的定义如下:
#define UV_HANDLE_FIELDS \ /* public */ \ uv_close_cb close_cb; \ void* data; \ /* read-only */ \ uv_loop_t* loop; \ uv_handle_type type; \ /* private */ \ void* handle_queue[2]; \ UV_HANDLE_PRIVATE_FIELDS \显然,二者是不同的。但还不能说,uv_loop_s就不是一个监视器。除了这个uv_loop_s外,其他所有的uv_TYPE_s的声明我都检查了一遍,发现其他所有uv_TYPE_s都“继承”自uv_handle_s,也就是说其他所有的uv_TYPE_s结构体与uv_handle_s结构体在内存区域的前部有着相同的结构。如果是这样,那么除了uv_loop_s外,所有其它的uv_TYPE_s都是监视器类型。我想这里应该就是所有监视器类型了,否则不用放在一块。
使用监视器
监视器使用前必须被初始化。一般情况下,libuv库为这些初始化函数取了相似的名称,均为uv_TYPE_init。监视器被初始化后必须调用uv_TYPE_start函数用来启动监视器。当不再需要监视器继续运作时需调用uv_TYPE_stop函数。符合这种使用模式的监视器只占半数。有这么一些:uv_timer_t、uv_prepare_t、uv_check_t、uv_idle_t、uv_fs_event_t、uv_fs_poll_t和uv_signal_t。几乎所有的非基类或非父类监视器都有一个形如uv_TYPE_init的初始化函数。只有一个监视器没有那就是uv_process_t。
与uv_process_t相关的函数有:uv_spawn和uv_process_kill。uv_spawn的作用相当于uv_TYPE_init和uv_TYPE_start。uv_process_kill的作用类似于uv_TYPE_stop。有一点必须提示的是,除了调用uv_process_kill函数外关闭uv_process_t还必须再调用uv_close函数。这是libuv库针对uv_process_t的特殊处理。如果不调用将会造成何种后果不得而知,因为uv.h头文件的注释中并未明确给出相关信息。
uv_tcp_t
与uv_tcp_t相关的函数有:uv_tcp_init、uv_listen、uv_accept、uv_close、uv_tcp_open、uv_tcp_nodelay、uv_tcp_keepalive、uv_tcp_simultaneous_accepts、uv_tcp_bind、uv_write、uv_read_start、uv_tcp_getsockname、uv_tcp_getpeername和uv_tcp_connect。
服务端。首先得使用uv_tcp_init初始化监视器。接着使用uv_tcp_bind将监视器绑定到特定的地址和端口。使用uv_tcp_bind前可以使用uv_ip4_addr函数生成uv_tcp_bind可以使用的参数变量。然后是使用uv_listen启动监视器,并提供回调函数。uv_listen函数内需要的回调函数类型是uv_connection_cb。uv_connection_cb回调函数将在有客户端连接服务端时被调用。在uv_connection_cb回调函数内我们将再次依次使用uv_tcp_init和uv_accept,用来为此客户端连接新建一个监视器,以及接受这个连接请求。接着可以在任何时候使用uv_write向对端发送异步写请求,同时提供回调函数。回调函数类型为uv_write_cb。使用uv_write函数时,还将牵扯到这两个结构体:uv_buf_t和uv_write_t。关闭客户端连接可以使用uv_close函数。这个函数也需提供一个回调函数,类型为uv_close_cb。uv_close函数还需一个连接句柄,此句柄就是为客户端连接创建的监视器,此时可使用uv_write_t结构体的handle属性。此handle值无需在创建uv_write_t变量时显示赋值。在使用uv_write后,libuv会在内部为uv_write_t变量的handle属性赋上正确的值。这个值即为连接上的客户端的uv_tcp_t值。我们会在接受客户端连接回调函数uv_connection_cb内创建的这个表示客户端的uv_tcp_t变量。由于这个表示客户端的uv_tcp_t变量是由客户代码显示创建的,所以也必须由客户代码显示删除它。删除这个变量的时机就在关闭客户端连接回调函数内。除了向对端发送数据外,还需收取从对端发送来的数据。此时用到的函数不再是uv_tcp开头的函数,而是使用uv_read_start。需要的输入参数为,连接上的客户端uv_tcp_t变量值,读取数据前分配缓冲区的回调函数,以及实际读取操作的回调函数。
下面看一个示例:
init_net()是初始化函数。只有read_data函数内有特定应用相关的内容。Gb1212ToUTF_8函数未列出。大致的过程是:监听TCP端口35755,接受其他客户端连接请求并等待接收客户端发来的数据,处理客户端的请求并向其发送相关数据,数据发送完毕后关闭与此客户端的连接。这份代码里唯一不确定的是after_uv_shutdown函数内是否还有必要调用uv_close。我尝试过即便after_uv_shutdown函数内不调用uv_close代码也能正常执行,加上这句代码也能正常执行。
void after_uv_close(uv_handle_t * handle) { free(handle); } void after_uv_shutdown(uv_shutdown_t* req, int status) { uv_close((uv_handle_t *)req->handle, after_uv_close); free(req); } void write_data(uv_write_t * req, int status) { if ( status == -1 ) { return; } char * base = (char *)req->data; free(base); uv_shutdown_t * shutdownReq = new uv_shutdown_t; uv_shutdown(shutdownReq, req->handle, after_uv_shutdown); free(req); } uv_buf_t alloc_before_read(uv_handle_t* handle, size_t suggested_size) { return uv_buf_init((char*)malloc(suggested_size), suggested_size); } void read_data(uv_stream_t* stream, ssize_t nread, uv_buf_t buf) { if ( nread < 0 ) { delete buf.base; uv_close((uv_handle_t *)stream, after_uv_close); return; } buf.base[nread] = 0; std::string::size_type pos = g_recvdata.find("/realtimeinfo/ext.html"); if ( pos != std::string::npos ) { std::string sendText1 = "<\?xml version=\"1.0\" encoding=\"UTF-8\"\?><RealtimeInfo><Extensions>" + pangu.m_TSAPIRealTimeStatusString.AssembleAllExtensionStatusString() + "</Extensions></RealtimeInfo>"; uv_write_t * req = (uv_write_t *)malloc(sizeof(uv_write_t)); strcpy((char *)buf.base, "HTTP/1.0 200 Okay\r\nContent-Type:text/xml\r\n\r\n"); req->data = (void*)buf.base; buf.len = strlen((char *)buf.base); uv_write(req, stream, &buf, 1, write_data); uv_buf_t buf1 = alloc_before_read((uv_handle_t *)stream, 65536); char * utf8Buf = Gb2312ToUTF_8(sendText1.c_str()); strcpy((char *)buf1.base, utf8Buf); delete []utf8Buf; uv_write_t * req1 = (uv_write_t *)malloc(sizeof(uv_write_t)); req1->data = (void*)buf1.base; buf1.len = sendText1.length(); uv_write(req1, stream, &buf1, 1, write_data); LOG4CXX_INFO(g_zhougongLog, sendText1.c_str()); } else if ( ( pos = g_recvdata.find("/realtimeinfo/grp.html") ) != std::string::npos ) { std::string sendText1 = "<\?xml version=\"1.0\" encoding=\"UTF-8\"\?><RealtimeInfo><Groups>" + pangu.m_TSAPIRealTimeStatusString.AssembleAllGroupStatusString() + "</Groups></RealtimeInfo>"; uv_write_t * req = (uv_write_t *)malloc(sizeof(uv_write_t)); strcpy((char *)buf.base, "HTTP/1.0 200 Okay\r\nContent-Type:text/xml\r\n\r\n"); req->data = (void*)buf.base; buf.len = strlen((char *)buf.base); uv_write(req, stream, &buf, 1, write_data); uv_buf_t buf1 = alloc_before_read((uv_handle_t *)stream, 65536); char * utf8Buf = Gb2312ToUTF_8(sendText1.c_str()); strcpy((char *)buf1.base, utf8Buf); delete []utf8Buf; uv_write_t * req1 = (uv_write_t *)malloc(sizeof(uv_write_t)); req1->data = (void*)buf1.base; buf1.len = sendText1.length(); uv_write(req1, stream, &buf1, 1, write_data); LOG4CXX_INFO(g_zhougongLog, sendText1.c_str()); } else if ( ( pos = g_recvdata.find("/realtimeinfo/vdnqueue.html") ) != std::string::npos ) { std::string sendText1 = "<\?xml version=\"1.0\" encoding=\"UTF-8\"\?><RealtimeInfo><VDNQueues>" + pangu.m_TSAPIRealTimeStatusString.AssembleAllVDNStatusString() + "</VDNQueues></RealtimeInfo>"; uv_write_t * req = (uv_write_t *)malloc(sizeof(uv_write_t)); strcpy((char *)buf.base, "HTTP/1.0 200 Okay\r\nContent-Type:text/xml\r\n\r\n"); req->data = (void*)buf.base; buf.len = strlen((char *)buf.base); uv_write(req, stream, &buf, 1, write_data); uv_buf_t buf1 = alloc_before_read((uv_handle_t *)stream, 65536); char * utf8Buf = Gb2312ToUTF_8(sendText1.c_str()); strcpy((char *)buf1.base, utf8Buf); delete []utf8Buf; uv_write_t * req1 = (uv_write_t *)malloc(sizeof(uv_write_t)); req1->data = (void*)buf1.base; buf1.len = sendText1.length(); uv_write(req1, stream, &buf1, 1, write_data); LOG4CXX_INFO(g_zhougongLog, sendText1.c_str()); } else if ( ( pos = g_recvdata.find("/realtimeinfo/livecallsdetail.html") ) != std::string::npos ) { std::string sendText1 = "<\?xml version=\"1.0\" encoding=\"UTF-8\"\?><RealtimeInfo><LiveCallsDetail>" + pangu.GetAllLiveCalls() + "</LiveCallsDetail></RealtimeInfo>"; uv_write_t * req = (uv_write_t *)malloc(sizeof(uv_write_t)); strcpy((char *)buf.base, "HTTP/1.0 200 Okay\r\nContent-Type:text/xml\r\n\r\n"); req->data = (void*)buf.base; buf.len = strlen((char *)buf.base); uv_write(req, stream, &buf, 1, write_data); uv_buf_t buf1 = alloc_before_read((uv_handle_t *)stream, 65536); char * utf8Buf = Gb2312ToUTF_8(sendText1.c_str()); strcpy((char *)buf1.base, utf8Buf); uv_write_t * req1 = (uv_write_t *)malloc(sizeof(uv_write_t)); req1->data = (void*)buf1.base; buf1.len = sendText1.length(); uv_write(req1, stream, &buf1, 1, write_data); delete []utf8Buf; LOG4CXX_INFO(g_zhougongLog, sendText1.c_str()); } else if ( ( pos = g_recvdata.find("/realtimeinfo/synctime.html") ) != std::string::npos ) { std::string sendText1 = "<\?xml version=\"1.0\" encoding=\"UTF-8\"\?><RealtimeInfo><SyncTime>" + ConvertCurrentDateTime() + "</SyncTime></RealtimeInfo>"; uv_write_t * req = (uv_write_t *)malloc(sizeof(uv_write_t)); strcpy((char *)buf.base, "HTTP/1.0 200 Okay\r\nContent-Type:text/xml\r\n\r\n"); req->data = (void*)buf.base; buf.len = strlen((char *)buf.base); uv_write(req, stream, &buf, 1, write_data); uv_buf_t buf1 = alloc_before_read((uv_handle_t *)stream, 65536); char * utf8Buf = Gb2312ToUTF_8(sendText1.c_str()); strcpy((char *)buf1.base, utf8Buf); uv_write_t * req1 = (uv_write_t *)malloc(sizeof(uv_write_t)); req1->data = (void*)buf1.base; buf1.len = sendText1.length(); uv_write(req1, stream, &buf1, 1, write_data); delete []utf8Buf; LOG4CXX_INFO(g_zhougongLog, sendText1.c_str()); } else free(buf.base); } void on_new_connection(uv_stream_t * server, int status) { if ( status == 0 ) { uv_tcp_t * client = (uv_tcp_t*)malloc(sizeof(uv_tcp_t)); uv_tcp_init(loop, client); if ( uv_accept(server, (uv_stream_t *)client) == 0 ) uv_read_start((uv_stream_t*)client, alloc_before_read, read_data); else uv_close((uv_handle_t *)client, NULL); } } void init_net() { struct sockaddr_in bind_addr = uv_ip4_addr("0.0.0.0", 35755); uv_tcp_init(loop, &server); uv_tcp_bind(&server, bind_addr); int ret = uv_listen((uv_stream_t *)&server, 128, on_new_connection); if ( ret ) { g_bInitNet = false; return; } g_bInitNet = true; }
客户端。首先也得使用uv_tcp_init初始化监视器。接着使用uv_tcp_connect函数连接服务端。uv_tcp_connect函数需要一个uv_connect_t类型的请求对象、一个sockaddr_in地址结构变量和uv_connect_cb类型的回调函数。地址结构变量可用uv_ip4_addr函数生成。在uv_connect_cb类型的回调函数内可启动读取或写入操作。关闭与服务端的连接,也就是调用uv_close关闭监视器。无论是服务端还是客户端都有很多需要注意的地方,稍后在其它文章中详细说明。
与uv_udp_t相关的函数有:uv_udp_init、uv_udp_bind、uv_udp_getsockname、uv_udp_set_membership、uv_udp_set_multicast_loop、uv_udp_set_multicast_ttl、uv_udp_set_broadcast、uv_udp_set_ttl、uv_udp_send、uv_udp_recv_start和uv_udp_recv_stop。
与uv_pipe_t相关的函数有:uv_pipe_init、uv_pipe_open、uv_pipe_bind、uv_pipe_connect和uv_pipe_pending_instances。
与uv_pipe_t相关的函数有:uv_pipe_init、uv_pipe_open、uv_pipe_bind、uv_pipe_connect和uv_pipe_pending_instances。
与uv_tty_t相关的函数有:uv_tty_init、uv_tty_set_mode、uv_tty_reset_mode和uv_tty_get_winsize。
与uv_pool_t相关的函数有:uv_pool_init、uv_pool_init_socket、uv_pool_start和uv_pool_stop。
与uv_async_t相关的函数有:uv_async_init和uv_async_send。