libuv - 监视器watchers

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
};

在浏览uv.h头文件时发现了这样一块内容:

/* 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_handle_s结构体。“继承”的意思是指它们有着相同的头部内存区块。我的疑问是,为何libuv不使用C++语言的继承机制来达成这个目的。是因为libuv希望也能在C语言环境下使用?还是C++语言的对象机制造成的对象内存布局无法满足libuv对于内存布局的要求?我想前者的可能性更大些,为了在更多的环境下被使用。


使用监视器

监视器使用前必须被初始化。一般情况下,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_spawnuv_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_acceptsuv_tcp_bind、uv_write、uv_read_start、uv_tcp_getsockname、uv_tcp_getpeernameuv_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_inituv_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_startuv_udp_recv_stop

uv_pipe_t相关的函数有:uv_pipe_inituv_pipe_open、uv_pipe_bind、uv_pipe_connectuv_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。




你可能感兴趣的:(libuv)