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\"\?>" + pangu.m_TSAPIRealTimeStatusString.AssembleAllExtensionStatusString() + " ";
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\"\?>" + pangu.m_TSAPIRealTimeStatusString.AssembleAllGroupStatusString() + " ";
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\"\?>" + pangu.m_TSAPIRealTimeStatusString.AssembleAllVDNStatusString() + " ";
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\"\?>" + pangu.GetAllLiveCalls() + " ";
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\"\?>" + ConvertCurrentDateTime() + " ";
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。