libuv 0.10 -> 1.0.0 迁移指南

一些API在1.0.0开发过程当中发生了很大的变化。 这是一份迁移指南,关于在0.10发布后发生的最重要的变化。

循环初始化和关闭

在libuv 0.10(和任何之前的版本中),循环通过 uv_loop_new 创建, 为新的循环分配内存且初始化它;同时通过 uv_loop_delete, 销毁循环且释放内存。 从1.0开始,这些被废弃了 并且用户负责分配内存和初始化循环。

libuv 0.10

uv_loop_t* loop = uv_loop_new();
...
uv_loop_delete(loop);

libuv 1.0

uv_loop_t* loop = malloc(sizeof *loop);
uv_loop_init(loop);
...
uv_loop_close(loop);
free(loop);

注解

错误处理为了简洁被省略了。 查看文档 uv_loop_init()uv_loop_close().

错误处理

错误处理在libuv 1.0中有巨大的翻修。 总的来说,在libuv 0.10中,函数和状态参数里 0 代表成功和 -1 代表失败,用户不得不使用 uv_last_error 获取错误代码,这是个正数。

在1.0中,函数和状态参数包括了确切的错误代码,0 代表成功, 错误的时候为一个负数。

libuv 0.10

... 假如 'server' 是一个已经在侦听中的TCP服务器
r = uv_listen((uv_stream_t*) server, 511, NULL);
if (r == -1) {
  uv_err_t err = uv_last_error(uv_default_loop());
  /* err.code 包括 UV_EADDRINUSE */
}

libuv 1.0

... 假如 'server' 是一个已经在侦听中的TCP服务器
r = uv_listen((uv_stream_t*) server, 511, NULL);
if (r < 0) {
  /* r 包括 UV_EADDRINUSE */
}

线程池变化

在libuv 0.10当中,Unix下用了一个默认是4个线程的线程池,而Windows下用了 QueueUserWorkItem API,这使用了一个Windows内部的线程池,默认是每个进程 512 个线程。

在1.0中,我们统一了两种实现,这样Windows现在使用和Unix同样的实现。 线程池的大小可以被外部环境变量 UV_THREADPOOL_SIZE 设置。详见 Thread pool work scheduling

分配回调函数 API变化

在libuv 0.10中,回调函数必须返回一个 uv_buf_t 类型的填充值:

uv_buf_t alloc_cb(uv_handle_t* handle, size_t size) {
    return uv_buf_init(malloc(size), size);
}

在libuv 1.0中,一个指向一个buffer的指针传递给回调函数,用户需要填充它:

void alloc_cb(uv_handle_t* handle, size_t size, uv_buf_t* buf) {
    buf->base = malloc(size);
    buf->len = size;
}

IPv4 / IPv6 API的统一

libuv 1.0统一了IPv4和IPv6 API。 再也没有 uv_tcp_binduv_tcp_bind6 这样的二元性, 现在只有 uv_tcp_bind()

IPv4函数采用 struct sockaddr_in 结构体的值,IPv6函数采用 struct sockaddr_in6。现在,函数采用 struct sockaddr* (注意,它是一个指针)。 它能在栈上分配。

libuv 0.10

struct sockaddr_in addr = uv_ip4_addr("0.0.0.0", 1234);
...
uv_tcp_bind(&server, addr)

libuv 1.0

struct sockaddr_in addr;
uv_ip4_addr("0.0.0.0", 1234, &addr)
...
uv_tcp_bind(&server, (const struct sockaddr*) &addr, 0);

IPv4和IPv6结构创建函数( uv_ip4_addr()uv_ip6_addr() ) 也变了,确保使用前你查看了文档。

注解

这些变化适用于所有区分IPv4和IPv6地址的函数。

流 / UDP数据接收回调函数 API变化

流和UDP数据接收回调函数现在接受一个指向 uv_buf_t 缓冲区的指针, 而不是一个结构体值。

libuv 0.10

void on_read(uv_stream_t* handle,
             ssize_t nread,
             uv_buf_t buf) {
    ...
}

void recv_cb(uv_udp_t* handle,
             ssize_t nread,
             uv_buf_t buf,
             struct sockaddr* addr,
             unsigned flags) {
    ...
}

libuv 1.0

void on_read(uv_stream_t* handle,
             ssize_t nread,
             const uv_buf_t* buf) {
    ...
}

void recv_cb(uv_udp_t* handle,
             ssize_t nread,
             const uv_buf_t* buf,
             const struct sockaddr* addr,
             unsigned flags) {
    ...
}

通过管道的接收句柄 API变化

在libuv 0.10(和之前版本)中, uv_read2_start 函数被用来开始在管道上读数据, 可能致使在其上接收句柄。 这类函数的回调函数看起来像这样:

void on_read(uv_pipe_t* pipe,
             ssize_t nread,
             uv_buf_t buf,
             uv_handle_type pending) {
    ...
}

在libuv 1.0中, uv_read2_start 被移除了,并且在读回调函数当中, 用户需要检查是否有待处理的句柄,使用 uv_pipe_pending_count()uv_pipe_pending_type()

void on_read(uv_stream_t* handle,
             ssize_t nread,
             const uv_buf_t* buf) {
    ...
    while (uv_pipe_pending_count((uv_pipe_t*) handle) != 0) {
        pending = uv_pipe_pending_type((uv_pipe_t*) handle);
        ...
    }
    ...
}

从句柄里提取文件描述符

当它还不被API支持的时候,用户通常访问libuv内部变量, 以获取例如TCP句柄的文件访问符。

fd = handle->io_watcher.fd;

这现在已经正式地暴露于 uv_fileno() 函数。

uv_fs_readdir重命名和API变化

在libuv 0.10中,当完成时 uv_fs_readdir 返回一个字符串列表于 req->ptr 字段。 在1.0中,这个函数被重命名为 uv_fs_scandir() , 因为这确实通过 scandir(3) 实现。

另外,用户可以使用 uv_fs_scandir_next() 函数一次获取一个结果,而不是分配完整的列表字符串。 这个函数不需要往返于线程池,因为libuv将保持 scandir(3) 返回的 凹齿 列表在一旁。