- 定时器句柄(uv_timer_t)
在 libuv 中,uv_timer_t 是用于实现定时器功能的句柄。定时器在很多应用场景中都非常有用,比如定时任务的执行、超时处理等。通过使用 uv_timer_t 句柄,开发者可以方便地在指定的时间点或按一定的时间间隔执行特定的代码逻辑,并且利用 libuv 的异步特性,不会阻塞主线程,从而保证程序的高效运行。
- 结构体剖析
uv_timer_t 结构体继承自 uv_handle_t,以下是其大致的结构和关键成员分析:
#define UV_TIMER_PRIVATE_FIELDS \
uv_timer_cb timer_cb; \
union { \
void* heap[3]; \
struct uv__queue queue; \
} node; \
uint64_t timeout; \
uint64_t repeat; \
uint64_t start_id;
/*
* uv_timer_t is a subclass of uv_handle_t.
*
* Used to get woken up at a specified time in the future.
*/
struct uv_timer_s {
UV_HANDLE_FIELDS
UV_TIMER_PRIVATE_FIELDS
};
UV_TIMER_PRIVATE_FIELDS定义的其他字段(unix版本):
- uv_timer_cb timer_cb;
- 用途:这是一个函数指针,指向定时器触发时要执行的回调函数。当定时器达到设定的时间(首次触发或重复触发)时,libuv 会调用这个回调函数。回调函数的原型为 void (*uv_timer_cb)(uv_timer_t* handle);,其中 handle 是指向当前定时器句柄的指针,通过这个指针可以在回调函数中访问定时器的相关信息。
- 示例场景:在一个游戏开发中,可能需要定时更新游戏角色的状态,比如每 100 毫秒更新一次角色的位置。可以将更新角色位置的函数作为 timer_cb 回调函数,当定时器触发时,就会执行该函数来更新角色位置。
- union { void* heap[3]; struct uv__queue queue; } node;
- 用途:这是一个联合体,提供了两种不同的数据结构表示方式,libuv 会根据具体的使用场景选择合适的方式。
- void* heap[3];:用于在堆中存储定时器节点的相关信息。在 libuv 的内部实现中,定时器可能会被组织成一个堆数据结构(通常是最小堆),通过这个数组来维护定时器节点在堆中的位置和相关指针。堆数据结构可以高效地管理定时器的触发顺序,确保最早到期的定时器优先被处理。
- struct uv__queue queue;:uv__queue 是 libuv 内部实现的一个队列结构,用于将定时器节点插入到某个队列中。例如,在定时器的启动、停止等操作中,可能需要将定时器节点从一个队列移动到另一个队列,或者将其从队列中移除。通过这个队列结构,可以方便地进行这些操作。
- 示例场景:在定时器的调度过程中,如果使用堆来管理定时器,heap 数组会记录定时器在堆中的位置,以便在定时器时间更新时能快速调整堆的结构;当需要暂停一批定时器时,可以将这些定时器节点插入到一个暂停队列中,使用 queue 结构来实现这种队列操作。
- uint64_t timeout;
- 用途:表示定时器首次触发的延迟时间,单位是毫秒。当调用 uv_timer_start 函数启动定时器时,会设置这个值。定时器会在启动后经过 timeout 毫秒触发第一次回调函数。
- 示例场景:在一个网络应用中,需要在连接建立后 5 秒发送一个心跳包。可以将 timeout 设置为 5000 毫秒,当定时器启动后,经过 5 秒就会触发回调函数,在回调函数中发送心跳包。
- uint64_t repeat;
- 用途:表示定时器重复触发的间隔时间,单位也是毫秒。如果 repeat 设置为 0,则定时器只触发一次;如果设置为一个非零值,定时器会在首次触发后,每隔 repeat 毫秒再次触发回调函数。
- 示例场景:在一个监控系统中,需要每隔 10 秒采集一次服务器的性能数据。可以将 repeat 设置为 10000 毫秒,这样定时器会在首次触发后,每隔 10 秒触发一次回调函数,在回调函数中采集服务器的性能数据。
- uint64_t start_id;
- 用途:这是一个唯一的标识符,用于标识定时器的启动事件。在 libuv 的内部实现中,可能会使用这个标识符来跟踪定时器的启动状态、处理定时器的重复启动等情况。每个定时器启动时会被分配一个唯一的 start_id,可以帮助区分不同的启动事件,避免出现混淆。
- 示例场景:当多次调用 uv_timer_start 函数启动同一个定时器时,通过 start_id 可以区分每次启动操作,确保定时器的触发逻辑按照预期执行。例如,在某些情况下,可能需要根据不同的启动事件执行不同的处理逻辑,通过 start_id 可以实现这种区分。
- 常见操作及示例代码
初始化
在使用 uv_timer_t 句柄之前,需要对其进行初始化。可以使用 uv_timer_init 函数来完成初始化操作,将句柄与指定的事件循环关联起来。
#include
#include
uv_loop_t* loop;
uv_timer_t timer_handle;
int main() {
loop = uv_default_loop();
// 初始化定时器句柄
int r = uv_timer_init(loop, &timer_handle);
if (r) {
fprintf(stderr, "Timer handle initialization error: %s\n", uv_strerror(r));
return 1;
}
// 后续操作...
return uv_run(loop, UV_RUN_DEFAULT);
}
启动定时器
初始化完成后,可以使用 uv_timer_start 函数来启动定时器,并设置首次触发的延迟时间和重复间隔时间。
#include
#include
uv_loop_t* loop;
uv_timer_t timer_handle;
void timer_callback(uv_timer_t* handle) {
printf("Timer fired!\n");
}
int main() {
loop = uv_default_loop();
uv_timer_init(loop, &timer_handle);
// 启动定时器,延迟 2000 毫秒后触发,之后每隔 3000 毫秒触发一次
int r = uv_timer_start(&timer_handle, timer_callback, 2000, 3000);
if (r) {
fprintf(stderr, "Timer start error: %s\n", uv_strerror(r));
return 1;
}
return uv_run(loop, UV_RUN_DEFAULT);
}
停止定时器
如果需要停止正在运行的定时器,可以使用 uv_timer_stop 函数。
#include
#include
uv_loop_t* loop;
uv_timer_t timer_handle;
int count = 0;
void timer_callback(uv_timer_t* handle) {
printf("Timer fired! Count: %d\n", ++count);
if (count >= 3) {
// 停止定时器
uv_timer_stop(handle);
}
}
int main() {
loop = uv_default_loop();
uv_timer_init(loop, &timer_handle);
uv_timer_start(&timer_handle, timer_callback, 1000, 1000);
return uv_run(loop, UV_RUN_DEFAULT);
}
重新设置定时器
可以使用 uv_timer_again 函数重新启动定时器,它会使用之前设置的 repeat 时间间隔。另外,还可以使用 uv_timer_set_repeat 函数来修改定时器的重复间隔时间。
#include
#include
uv_loop_t* loop;
uv_timer_t timer_handle;
int count = 0;
void timer_callback(uv_timer_t* handle) {
printf("Timer fired! Count: %d\n", ++count);
if (count == 2) {
// 修改重复间隔时间为 2000 毫秒
uv_timer_set_repeat(handle, 2000);
}
if (count >= 4) {
// 重新启动定时器,使用新的重复间隔时间
uv_timer_again(handle);
}
}
int main() {
loop = uv_default_loop();
uv_timer_init(loop, &timer_handle);
uv_timer_start(&timer_handle, timer_callback, 1000, 1000);
return uv_run(loop, UV_RUN_DEFAULT);
}
关闭定时器句柄
当不再需要使用定时器时,需要关闭句柄以释放相关资源。可以使用 uv_close 函数来关闭定时器句柄,并指定关闭完成后的回调函数。
#include
#include
uv_loop_t* loop;
uv_timer_t timer_handle;
int count = 0;
void timer_callback(uv_timer_t* handle) {
printf("Timer fired! Count: %d\n", ++count);
if (count >= 3) {
// 关闭定时器句柄
uv_close((uv_handle_t*)handle, [](uv_handle_t* handle) {
printf("Timer handle closed\n");
});
}
}
int main() {
loop = uv_default_loop();
uv_timer_init(loop, &timer_handle);
uv_timer_start(&timer_handle, timer_callback, 1000, 1000);
return uv_run(loop, UV_RUN_DEFAULT);
}
- 注意事项
- 资源管理:确保在不再使用定时器时,及时调用 uv_close 函数关闭句柄,避免资源泄漏。同时,如果在定时器的回调函数中使用了动态分配的内存,要在合适的时候释放这些内存。
- 时间精度:虽然 libuv 的定时器可以提供相对精确的定时功能,但由于操作系统的调度和其他因素的影响,实际的触发时间可能会有一定的误差。在对时间精度要求极高的场景下,需要考虑这些因素。
- 回调函数中的操作:定时器的回调函数会在事件循环中执行,因此要避免在回调函数中进行耗时的操作,以免阻塞事件循环,影响其他任务的执行。如果需要进行耗时操作,可以考虑使用异步工作请求(uv_work_t)将其放到后台线程中执行。
- 文件系统事件句柄(uv_fs_event_t)
在 libuv 中,uv_fs_event_t 是用于监控文件系统事件的句柄。它允许程序对指定的文件或目录进行实时监控,当文件或目录发生特定的变化(如文件的创建、删除、修改等)时,程序能够及时得到通知并做出相应的处理。这在很多应用场景中都非常有用,例如文件同步工具、代码热加载机制等,通过监控文件系统事件可以实现自动化的任务。
- 结构体剖析
uv_fs_event_t 结构体继承自 uv_handle_t,以下是其大致结构和关键成员分析:
#define UV_PLATFORM_FS_EVENT_FIELDS \
struct uv__queue watchers; \
int wd; \
#define UV_FS_EVENT_PRIVATE_FIELDS \
uv_fs_event_cb cb; \
UV_PLATFORM_FS_EVENT_FIELDS \
struct uv_fs_event_s {
UV_HANDLE_FIELDS
/* private */
char* path;
UV_FS_EVENT_PRIVATE_FIELDS
};
UV_FS_EVENT_PRIVATE_FIELDS定义的其他字段(unix版本):
- uv_fs_event_cb cb;
- 用途:uv_fs_event_cb 是一个回调函数类型,cb 字段存储的是当文件系统事件发生时要调用的回调函数指针。当监控的文件或目录发生诸如文件创建、删除、修改等事件时,libuv 会调用这个回调函数,并将相关的事件信息(如文件名、事件类型、状态码等)传递给它,开发者可以在回调函数中编写具体的处理逻辑。
- 示例场景:在一个文件同步工具中,当监控的目录中有新文件创建时,回调函数可以将新文件同步到其他设备或备份位置。
UV_PLATFORM_FS_EVENT_FIELDS 宏定义分析:
- struct uv__queue watchers;
- 用途:uv__queue 是 libuv 内部实现的一个队列结构。watchers 队列用于管理所有相关的文件系统监控器。在 uv_fs_event_t 句柄监控文件系统时,可能会有多个监控任务或者与其他监控器存在关联,通过这个队列可以将这些监控器组织起来,方便 libuv 对它们进行统一管理,例如添加、删除、遍历监控器等操作。
- 示例场景:在一个复杂的应用程序中,可能同时需要监控多个不同的文件或目录,每个监控任务都对应一个监控器。这些监控器会被添加到 watchers 队列中,当需要停止某个监控任务或者更新监控配置时,libuv 可以通过遍历这个队列找到对应的监控器进行操作。
- int wd;
- 用途:wd 通常代表文件系统监控描述符(watch descriptor)。在使用操作系统提供的文件系统监控机制(如 Linux 下的 inotify、macOS 下的 FSEvents 等)时,会为每个监控的文件或目录分配一个唯一的描述符。wd 就是用来存储这个描述符的,通过它可以在操作系统层面标识和管理具体的监控任务。
- 示例场景:在 Linux 系统中使用 inotify 监控文件系统事件,当调用 inotify_add_watch 函数添加一个监控任务时,会返回一个监控描述符。这个描述符会被存储在 wd 中,后续在处理文件系统事件时,libuv 可以根据这个描述符来确定是哪个监控任务触发了事件。
- 常见操作及示例代码
初始化
在使用 uv_fs_event_t 句柄之前,需要对其进行初始化。可以使用 uv_fs_event_init 函数来完成初始化操作,将句柄与指定的事件循环关联起来。
#include
#include
uv_loop_t* loop;
uv_fs_event_t fs_event_handle;
int main() {
loop = uv_default_loop();
// 初始化文件系统事件句柄
int r = uv_fs_event_init(loop, &fs_event_handle);
if (r) {
fprintf(stderr, "File system event handle initialization error: %s\n", uv_strerror(r));
return 1;
}
// 后续操作...
return uv_run(loop, UV_RUN_DEFAULT);
}
启动监控
初始化完成后,可以使用 uv_fs_event_start 函数来启动对指定文件或目录的监控,并设置事件回调函数和监控标志。
#include
#include
uv_loop_t* loop;
uv_fs_event_t fs_event_handle;
void fs_event_callback(uv_fs_event_t* handle, const char* filename, int events, int status) {
if (status) {
fprintf(stderr, "File system event error: %s\n", uv_strerror(status));
return;
}
if (events & UV_RENAME) {
printf("File or directory renamed: %s\n", filename? filename : handle->path);
}
if (events & UV_CHANGE) {
printf("File or directory changed: %s\n", filename? filename : handle->path);
}
}
int main() {
loop = uv_default_loop();
uv_fs_event_init(loop, &fs_event_handle);
// 启动监控,监控当前目录,设置递归监控标志
int r = uv_fs_event_start(&fs_event_handle, fs_event_callback, ".", UV_FS_EVENT_RECURSIVE);
if (r) {
fprintf(stderr, "File system event start error: %s\n", uv_strerror(r));
return 1;
}
return uv_run(loop, UV_RUN_DEFAULT);
}
在这个示例中,fs_event_callback 是文件系统事件发生时调用的回调函数。当文件或目录发生重命名(UV_RENAME)或内容更改(UV_CHANGE)事件时,会在回调函数中输出相应的信息。
停止监控
如果需要停止对文件或目录的监控,可以使用 uv_fs_event_stop 函数。
#include
#include
#include
uv_loop_t* loop;
uv_fs_event_t fs_event_handle;
void fs_event_callback(uv_fs_event_t* handle, const char* filename, int events, int status) {
if (status) {
fprintf(stderr, "File system event error: %s\n", uv_strerror(status));
return;
}
if (events & UV_CHANGE) {
printf("File or directory changed: %s\n", filename? filename : handle->path);
// 停止监控
uv_fs_event_stop(handle);
}
}
int main() {
loop = uv_default_loop();
uv_fs_event_init(loop, &fs_event_handle);
uv_fs_event_start(&fs_event_handle, fs_event_callback, ".", 0);
// 模拟程序运行一段时间
sleep(10);
return uv_run(loop, UV_RUN_DEFAULT);
}
在这个示例中,当检测到文件或目录发生更改事件时,在回调函数中调用 uv_fs_event_stop 函数停止监控。
关闭句柄
当不再需要使用文件系统事件句柄时,需要关闭句柄以释放相关资源。可以使用 uv_close 函数来关闭句柄,并指定关闭完成后的回调函数。
#include
#include
uv_loop_t* loop;
uv_fs_event_t fs_event_handle;
void fs_event_callback(uv_fs_event_t* handle, const char* filename, int events, int status) {
if (status) {
fprintf(stderr, "File system event error: %s\n", uv_strerror(status));
return;
}
if (events & UV_CHANGE) {
printf("File or directory changed: %s\n", filename? filename : handle->path);
// 关闭句柄
uv_close((uv_handle_t*)handle, [](uv_handle_t* handle) {
printf("File system event handle closed\n");
});
}
}
int main() {
loop = uv_default_loop();
uv_fs_event_init(loop, &fs_event_handle);
uv_fs_event_start(&fs_event_handle, fs_event_callback, ".", 0);
return uv_run(loop, UV_RUN_DEFAULT);
}
- 注意事项
- 权限问题:确保程序有足够的权限来监控指定的文件或目录。如果没有相应的权限,可能会导致监控失败。
- 跨平台差异:不同操作系统对文件系统事件的支持和实现可能存在差异。例如,某些操作系统可能对递归监控的支持有限,或者对某些事件的触发条件和频率有所不同。在编写跨平台的代码时,需要考虑这些差异。
- 事件回调函数的处理:文件系统事件可能会频繁触发,尤其是在一个活跃的目录中。在事件回调函数中,要避免进行耗时的操作,以免阻塞事件循环,影响其他任务的执行。如果需要进行耗时操作,可以考虑使用异步工作请求(uv_work_t)将其放到后台线程中执行。
- 管道句柄(uv_pipe_t)
在 libuv 中,uv_pipe_t 是用于实现管道通信的句柄。管道是一种在进程间或同一进程的不同线程间进行数据传输的机制,分为命名管道(Named Pipe)和匿名管道(Anonymous Pipe)。uv_pipe_t 提供了跨平台的统一接口,让开发者可以方便地使用管道进行数据通信,同时利用 libuv 的异步 I/O 特性,提高通信效率和程序的并发性能。管道通信常用于进程间的协同工作、数据传输等场景,例如一个进程生成数据,另一个进程对数据进行处理。
- 结构体剖析
uv_pipe_t 结构体继承自 uv_stream_t,而 uv_stream_t 又继承自 uv_handle_t,以下是其大致结构和关键成员分析:
#define UV_PIPE_PRIVATE_FIELDS \
const char* pipe_fname; /* NULL or strdup'ed */
/*
* uv_pipe_t is a subclass of uv_stream_t.
*
* Representing a pipe stream or pipe server. On Windows this is a Named
* Pipe. On Unix this is a Unix domain socket.
*/
struct uv_pipe_s {
UV_HANDLE_FIELDS
UV_STREAM_FIELDS
int ipc; /* non-zero if this pipe is used for passing handles */
UV_PIPE_PRIVATE_FIELDS
};
管道句柄特有的成员:
- int ipc;
- 用途:ipc 字段是一个整型标志,用于指示当前的 uv_pipe_t 管道是否用于进程间通信(Inter - Process Communication, IPC)。在进程间通信的场景中,管道不仅可以用于传输普通的数据,还可以用于传递文件描述符、句柄等系统资源,以实现更复杂的进程间协作。当 ipc 被设置为非零值时,表示该管道具备传递句柄等特殊资源的能力;若为 0,则表示该管道仅用于普通的数据传输。
- 示例场景:
- 传递文件描述符:在一个多进程的文件处理系统中,一个进程负责读取文件,另一个进程负责对读取的数据进行处理。读取进程可以通过设置 ipc 标志的管道将文件描述符传递给处理进程,处理进程可以直接使用该文件描述符进行数据处理,避免了数据的重复复制,提高了效率。
- 跨进程的服务调用:在分布式系统中,不同的进程可能提供不同的服务。通过设置 ipc 标志的管道,一个进程可以将服务请求的相关句柄(如套接字句柄)传递给另一个进程,实现跨进程的服务调用。
UV_PIPE_PRIVATE_FIELDS 宏定义分析
- const char* pipe_fname;
- 用途:pipe_fname 是一个指向字符串的常量指针,用于存储管道的文件名。对于命名管道(Named Pipe),需要一个唯一的名称来标识该管道,以便不同的进程可以通过这个名称来连接到同一个管道进行通信。pipe_fname 就是用来存储这个名称的,它通常是通过 strdup 函数复制得到的,以确保在不同的操作中可以安全地使用该名称,避免因内存管理问题导致的错误。
- 示例场景
- 创建和连接命名管道:在创建命名管道时,需要指定一个文件名作为管道的标识。例如,在服务器端创建命名管道时,可以将指定的文件名赋值给 pipe_fname,然后使用该名称进行绑定操作。客户端在连接时,也会使用相同的文件名来连接到这个命名管道。
- 常见操作及示例代码
初始化
在使用 uv_pipe_t 句柄之前,需要对其进行初始化。可以使用 uv_pipe_init 函数来完成初始化操作,将句柄与指定的事件循环关联起来,并可以指定是否用于进程间通信。
#include
#include
uv_loop_t* loop;
uv_pipe_t pipe_handle;
int main() {
loop = uv_default_loop();
// 初始化管道句柄,不用于进程间通信
int r = uv_pipe_init(loop, &pipe_handle, 0);
if (r) {
fprintf(stderr, "Pipe handle initialization error: %s\n", uv_strerror(r));
return 1;
}
// 后续操作...
return uv_run(loop, UV_RUN_DEFAULT);
}
绑定命名管道(可选)
如果使用命名管道进行通信,需要将管道句柄绑定到一个特定的名称上。可以使用 uv_pipe_bind 函数来完成绑定操作。
#include
#include
uv_loop_t* loop;
uv_pipe_t pipe_handle;
int main() {
loop = uv_default_loop();
uv_pipe_init(loop, &pipe_handle, 0);
// 绑定命名管道
int r = uv_pipe_bind(&pipe_handle, "my_pipe");
if (r) {
fprintf(stderr, "Pipe bind error: %s\n", uv_strerror(r));
return 1;
}
// 后续操作...
return uv_run(loop, UV_RUN_DEFAULT);
}
发起连接(客户端)
对于作为客户端的管道,需要发起连接请求。可以使用 uv_pipe_connect 函数来发起连接,连接成功或失败后会调用指定的回调函数。
#include
#include
uv_loop_t* loop;
uv_pipe_t pipe_handle;
uv_connect_t connect_req;
void connect_callback(uv_connect_t* req, int status) {
if (status < 0) {
fprintf(stderr, "Connect error: %s\n", uv_strerror(status));
} else {
printf("Connected to server\n");
// 连接成功,可以开始发送或接收数据
}
}
int main() {
loop = uv_default_loop();
uv_pipe_init(loop, &pipe_handle, 0);
// 发起连接请求
uv_pipe_connect(&connect_req, &pipe_handle, "my_pipe", connect_callback);
return uv_run(loop, UV_RUN_DEFAULT);
}
数据读写
连接建立后,客户端和服务器可以进行数据的读写操作。可以使用 uv_read_start 函数开始读取数据,使用 uv_write 函数发送数据。
读取数据示例:
#include
#include
#include
uv_loop_t* loop;
uv_pipe_t pipe_handle;
void alloc_buffer(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) {
buf->base = (char*)malloc(suggested_size);
buf->len = suggested_size;
}
void read_callback(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
if (nread > 0) {
printf("Read %zd bytes: %.*s\n", nread, (int)nread, buf->base);
} else if (nread < 0) {
if (nread != UV_EOF) {
fprintf(stderr, "Read error: %s\n", uv_strerror(nread));
}
// 关闭句柄
uv_close((uv_handle_t*)stream, NULL);
}
free(buf->base);
}
int main() {
loop = uv_default_loop();
uv_pipe_init(loop, &pipe_handle, 0);
// 假设已经建立连接...
// 开始读取数据
uv_read_start((uv_stream_t*)&pipe_handle, alloc_buffer, read_callback);
return uv_run(loop, UV_RUN_DEFAULT);
}
发送数据示例:
#include
#include
#include
uv_loop_t* loop;
uv_pipe_t pipe_handle;
uv_write_t write_req;
void write_callback(uv_write_t* req, int status) {
if (status < 0) {
fprintf(stderr, "Write error: %s\n", uv_strerror(status));
} else {
printf("Data written successfully\n");
}
}
int main() {
loop = uv_default_loop();
uv_pipe_init(loop, &pipe_handle, 0);
// 假设已经建立连接...
const char* data = "Hello, server!";
uv_buf_t buf = uv_buf_init((char*)data, strlen(data));
// 发送数据
uv_write(&write_req, (uv_stream_t*)&pipe_handle, &buf, 1, write_callback);
return uv_run(loop, UV_RUN_DEFAULT);
}
- 注意事项
- 资源管理:在使用 uv_pipe_t 句柄时,要注意资源的正确管理。例如,在创建新的句柄后,要确保在不再使用时调用 uv_close 函数关闭句柄,并在关闭回调函数中释放相关的资源。对于动态分配的缓冲区,要在使用完后及时释放,避免内存泄漏。
- 错误处理:在进行管道通信时,可能会出现各种错误,如连接失败、读写错误等。在每个操作的回调函数中,要检查返回的状态码,根据状态码进行相应的错误处理,输出详细的错误信息,方便调试和定位问题。
- 并发处理:由于 libuv 是基于异步 I/O 的,在处理多个连接时,要考虑并发问题。例如,在多线程环境下,要确保对共享资源的访问是线程安全的,可以使用锁机制来保证数据的一致性。
- 句柄的生命周期管理
- 句柄的创建
初始化函数的使用
在 libuv 中,不同类型的句柄有对应的初始化函数。这些初始化函数的主要作用是将句柄与事件循环关联起来,并进行必要的初始化操作。例如,对于 TCP 句柄,可以使用 uv_tcp_init 函数进行初始化:
uv_loop_t* loop = uv_default_loop();
uv_tcp_t tcp_handle;
uv_tcp_init(loop, &tcp_handle);
在这个示例中,uv_tcp_init 函数将 tcp_handle 句柄与默认的事件循环 loop 关联起来,并对 tcp_handle 进行初始化。
不同句柄类型的初始化差异
不同类型的句柄在初始化时可能会有一些差异。例如,定时器句柄 uv_timer_t 的初始化函数 uv_timer_init 只需要传入事件循环和句柄指针:
uv_loop_t* loop = uv_default_loop();
uv_timer_t timer_handle;
uv_timer_init(loop, &timer_handle);
而管道句柄 uv_pipe_t 的初始化函数 uv_pipe_init 除了需要传入事件循环和句柄指针外,还可以传入一个标志位,用于指定管道的类型:
uv_loop_t* loop = uv_default_loop();
uv_pipe_t pipe_handle;
uv_pipe_init(loop, &pipe_handle, 0); // 0 表示匿名管道
- 句柄的激活与使用
激活句柄的操作(如绑定、监听等)
句柄创建并初始化后,通常需要进行一些激活操作才能开始工作。例如,对于 TCP 服务器句柄,需要进行绑定和监听操作:
uv_loop_t* loop = uv_default_loop();
uv_tcp_t tcp_handle;
uv_tcp_init(loop, &tcp_handle);
struct sockaddr_in addr;
uv_ip4_addr("0.0.0.0", 8080, &addr);
// 绑定地址
uv_tcp_bind(&tcp_handle, (const struct sockaddr*)&addr, 0);
// 开始监听连接
uv_listen((uv_stream_t*)&tcp_handle, 128, on_new_connection);
在这个示例中,首先使用 uv_tcp_bind 函数将 TCP 句柄绑定到本地的0.0.0.0 地址的 8080 端口,然后使用 uv_listen 函数开始监听新的连接请求,当有新连接到来时会调用 on_new_connection 回调函数。
对于定时器句柄,激活操作就是启动定时器:
uv_loop_t* loop = uv_default_loop();
uv_timer_t timer_handle;
uv_timer_init(loop, &timer_handle);
// 启动定时器,延迟 2000 毫秒后触发,之后每隔 3000 毫秒触发一次
uv_timer_start(&timer_handle, timer_callback, 2000, 3000);
这里使用 uv_timer_start 函数来启动定时器,指定了首次触发的延迟时间和后续重复触发的间隔时间。
- 句柄激活后的状态变化
句柄激活后,其状态会发生相应的变化。以 TCP 句柄为例,在绑定和监听操作完成后,句柄就处于监听状态,开始等待客户端的连接请求。此时,句柄会被注册到事件循环中,当有新的连接事件发生时,事件循环会检测到并触发相应的回调函数。
对于定时器句柄,启动后会进入计时状态,当达到指定的时间后,会触发回调函数。在计时过程中,定时器句柄会持续占用系统资源,直到停止或关闭。
- 句柄的关闭
uv_close 函数的使用
当句柄不再需要使用时,需要将其关闭以释放相关资源。在 libuv 中,使用 uv_close 函数来关闭句柄:
void uv_close(uv_handle_t* handle, uv_close_cb close_cb);
其中,handle 是要关闭的句柄指针,close_cb 是关闭完成后的回调函数。例如,关闭一个 TCP 句柄:
uv_tcp_t tcp_handle;
// 初始化、激活等操作...
void on_close(uv_handle_t* handle) {
printf("TCP handle closed\n");
}
uv_close((uv_handle_t*)&tcp_handle, on_close);
在这个例子中,调用 uv_close 函数关闭 tcp_handle 句柄,当关闭操作完成后,会调用 on_close 回调函数。
- 关闭句柄时的回调函数处理
关闭句柄时的回调函数是一个重要的环节,它可以用于执行一些清理操作。例如,释放句柄所占用的额外资源、关闭相关的文件描述符等。在回调函数中,需要确保所有与句柄相关的资源都被正确释放,避免出现内存泄漏等问题。
uv_timer_t timer_handle;
// 初始化、启动等操作...
void on_timer_close(uv_handle_t* handle) {
// 可以在这里释放 timer_handle 关联的自定义数据
// 例如:
// free(handle->data);
printf("Timer handle closed\n");
}
uv_close((uv_handle_t*)&timer_handle, on_timer_close);
- 避免资源泄漏的注意事项
在关闭句柄时,要特别注意避免资源泄漏。除了在关闭回调函数中释放自定义资源外,还需要确保在句柄关闭前,所有与之相关的请求都已经完成。例如,如果一个 TCP 句柄上还有未完成的读写请求,直接关闭句柄可能会导致数据丢失或资源泄漏。可以在关闭句柄前,先停止所有的请求操作,等待请求完成后再关闭句柄。
另外,要确保在程序结束时,所有创建的句柄都被正确关闭。可以在程序的退出逻辑中添加关闭句柄的代码,保证资源的正确释放。