流【stream】
uv_stream_t
--- 流句柄
- 流句柄提供对双工通信通道的一种抽象。
uv_stream_t
是一个抽象类型,libuv提供了3种流的实现以uv_tcp_t
、uv_pipe_t
和uv_tty_t
的形式。
数据类型
-
写请求类型。 当复用这种对象时必须小心注意。 当一个流处在非阻塞模式,用
uv_write
发送的写请求将被排队。 在此刻复用对象是未定义的行为。 仅在传递给uv_write
的回调函数执行完毕后才能安全地复用uv_write_t
对象。
-
void
(*uv_read_cb)
(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf)数据在流上读取时的回调函数。
nread 是 > 0 如果有可用的数据,或 < 0 当错误时。 当我们已经到达EOF, nread 将被设置为
UV_EOF
。 当 nread < 0 时 buf 参数可能并不指向一个合法的缓冲区; 在那种情况下 buf.len 和 buf.base 都被设为0。
-
void
(*uv_write_cb)
(uv_write_t* req, int status)数据已经在流上写后的回调函数。 status 若成功将是0,否则 < 0。
-
void
(*uv_connect_cb)
(uv_connect_t* req, int status)以
uv_connect()
开启连接完成后的回调函数。 status 若成功将是0,否则 < 0。
-
void
(*uv_shutdown_cb)
(uv_shutdown_t* req, int status)停机请求完成后的回调函数。 status 若成功将是0,否则 < 0。
-
void
(*uv_connection_cb)
(uv_stream_t* server, int status)当流服务器接收到新来的连接时的回调函数。 用户能够通过调用
uv_accept()
来接受连接。 status 若成功将是0,否则 < 0。
API
-
int
uv_shutdown
(uv_shutdown_t* req, uv_stream_t* handle, uv_shutdown_cb cb)停机双工流的向外(写)端。 它等待未处理的写请求完成。 handle 应该指向已初始化的流。 req 应该是一个未初始化的停机请求结构体。 cb 在停机完成后被调用。
-
int
uv_listen
(uv_stream_t* stream, int backlog, uv_connection_cb cb)开始侦听新来的连接。 backlog
指内核可能排队的连接数,与 :man:
listen(2) 相同。 当接受到新来的连接时,调用uv_connection_cb
回调函数。
-
int
uv_accept
(uv_stream_t* server, uv_stream_t* client)调用用来配合
uv_listen()
接受新来的连接。 在接收到uv_connection_cb
后调用这个函数以接受连接。 在调用这个函数前,客户端句柄必须被初始化。 < 0 返回值表示错误。
-
int
uv_read_start
(uv_stream_t* stream, uv_alloc_cb alloc_cb, uv_read_cb read_cb)从内向的流读取数据。 将会调用
uv_read_cb
回调函数几次直到没有更多数据可读或是调用了uv_read_stop()
。
-
int
uv_read_stop
(uv_stream_t*)停止从流读取数据。
uv_read_cb
回调函数将不再被调用。这个函数是幂等的且可以在已停止的流上安全地被调用。
-
int
uv_write
(uv_write_t* req, uv_stream_t* handle, const uv_buf_t bufs[] , unsigned int nbufs, uv_write_cb cb)写数据到流。 缓冲区按序写入。
-
int
uv_write2
(uv_write_t* req, uv_stream_t* handle, const uv_buf_t bufs[] , unsigned int nbufs, uv_stream_t* send_handle, uv_write_cb cb)扩展的写函数用于在管道上发送句柄。 管道必须以 ipc == 1 初始化。
-
int
uv_try_write
(uv_stream_t* handle, const uv_buf_t bufs[] , unsigned int nbufs)与
uv_write()
相同,但是如果无法立刻完成时不会排队写请求。将返回以下之一:
>
0: 已写字节数(可能小于提供的缓冲区大小)。<
0: 负的错误代码(返回UV_EAGAIN
如果没有数据能立刻发送)。
-
int
uv_is_readable
(const uv_stream_t* handle)如果流可读返回1,否则0。
-
int
uv_is_writable
(const uv_stream_t* handle)如果流可写返回1,否则0。
-
int
uv_stream_set_blocking
(uv_stream_t* handle, int blocking)启用或禁用流的阻塞模式。
当阻塞模式开启时所有的写都是同步完成的。 别的界面保持不变,比如操作完成或失败将仍然通过回调函数异步被报告。
-
size_t
uv_stream_get_write_queue_size
(const uv_stream_t* stream)返回 stream->write_queue_size 。
源码分析
uv_stream_s
- 源码
struct uv_stream_s { // 句柄[handle]相关参数 void* data; uv_loop_t* loop; /* 所属事件循环 */ uv_handle_type type; /* handle 类型 */ uv_close_cb close_cb; /* 关闭 handle 时的回调 */ void* handle_queue[2]; /* 用于插入事件循环的 handle 队列 */ union { int fd; void* reserved[4]; } u; uv_handle_t* next_closing; /* 用于插入事件循环的 closing 阶段对应的队列 */ unsigned int flags; /* 各种标记 */ // 流[stream]相关参数 size_t write_queue_size; /* 用户写入流的字节大小,流缓存用户的输入,然后等到可写的时候才做真正的写 */ uv_alloc_cb alloc_cb; /* 分配内存的函数,内存由用户定义,主要用来保存读取的数据 */ uv_read_cb read_cb; /* 读取完成时候执行的回调函数 */ uv_connect_t *connect_req; /* 连接成功后,执行 connect_req 的回调(connect_req 在 uv__xxx_connect 中赋值) */ uv_shutdown_t *shutdown_req; /* 关闭写端的时候,发送完缓存的数据,执行 shutdown_req 的回调(shutdown_req 在 uv_shutdown 的时候赋值) */ uv__io_t io_watcher; /* 流对应的 io 观察者,即文件描述符+一个文件描述符事件触发时执行的回调 */ void* write_queue[2]; /* 流缓存下来的,待写的数据 */ void* write_completed_queue[2]; /* 已经完成了数据写入的队列 */ uv_connection_cb connection_cb; /* 完成三次握手后,执行的回调 */ int delayed_error; /* 操作流时出错码 */ int accepted_fd; /* accept 返回的通信 socket 对应的文件描述符 */ void* queued_fds; /* 同上,用于缓存更多的通信 socket 对应的文件描述符 */ UV_STREAM_PRIVATE_PLATFORM_FIELDS /* 目前为空 */ }
uv__stream_init
- 初始化一些字段
- 源码
void uv__stream_init(uv_loop_t* loop, uv_stream_t* stream, uv_handle_type type) { int err; // 调用uv__handle_init()函数将stream handle初始化,主要设置loop、类型、以及UV_HANDLE_REF标记 uv__handle_init(loop, (uv_handle_t*)stream, type); // 初始化stream handle中的成员变量 stream->read_cb = NULL; stream->alloc_cb = NULL; stream->close_cb = NULL; stream->connection_cb = NULL; stream->connect_req = NULL; stream->shutdown_req = NULL; stream->accepted_fd = -1; stream->queued_fds = NULL; stream->delayed_error = 0; // 初始化write_queue与write_completed_queue队列 QUEUE_INIT(&stream->write_queue); QUEUE_INIT(&stream->write_completed_queue); stream->write_queue_size = 0; // 这个逻辑看起来是为了拿到一个备用的文件描述符,如果以后触发 UV_EMFILE 错 // 误(打开的文件太多)时,使用这个备用的 fd if (loop->emfile_fd == -1) { err = uv__open_cloexec("/dev/null", O_RDONLY); if (err < 0) /* In the rare case that "/dev/null" isn't mounted open "/" * instead. */ err = uv__open_cloexec("/", O_RDONLY); if (err >= 0) loop->emfile_fd = err; } #if defined(__APPLE__) stream->select = NULL; #endif /* defined(__APPLE_) */ // 初始化 io 观察者,把文件描述符(这里还没有,所以是-1)和回调 uv__stream_io // 记录在 io_watcher 上 uv__io_init(&stream->io_watcher, uv__stream_io, -1); }
uv__stream_open
- 打开一个流,本质上就是给这个流关联一个文件描述符。还有一些属性的设置。 有了文件描述符,后续就可以操作这个流了
- 源码
// 关闭 nagle,开启长连接,保存 fd int uv__stream_open(uv_stream_t* stream, int fd, int flags) { #if defined(__APPLE__) int enable; #endif // 还没有设置 fd 或者设置的同一个 fd 则继续,否则返回 busy if (!(stream->io_watcher.fd == -1 || stream->io_watcher.fd == fd)) return UV_EBUSY; assert(fd >= 0); // 设置流的标记 stream->flags |= flags; if (stream->type == UV_TCP) { // 关闭 nagle 算法 if ((stream->flags & UV_HANDLE_TCP_NODELAY) && uv__tcp_nodelay(fd, 1)) return UV__ERR(errno); // 开启 SO_KEEPALIVE,使用 tcp 长连接,一定时间后没有收到数据包会发送心跳包 if ((stream->flags & UV_HANDLE_TCP_KEEPALIVE) && uv__tcp_keepalive(fd, 1, 60)) { return UV__ERR(errno); } } #if defined(__APPLE__) enable = 1; if (setsockopt(fd, SOL_SOCKET, SO_OOBINLINE, &enable, sizeof(enable)) && errno != ENOTSOCK && errno != EINVAL) { return UV__ERR(errno); } #endif // 保存 socket 对应的文件描述符到 io 观察者中,libuv 会在 io poll 阶段监听该文件描述符 stream->io_watcher.fd = fd; return 0; }
uv_read_start
- 执行 uv_read_start 本质上是给流对应的文件描述符在 epoll 中注册了一个可读事件。并且给一些字段赋值,比如读回调函数,分配内存的函数。打上正在做读取操作的标记。然后在可读事件触发的时候,读回调就会被执行
- 源码
// 当连接成功后,可以调用uv_read_start()函数去监听流的读取端,当有数据可读的时候, // 将会调用uv_read_cb指定的回调函数,递交到用户去处理这些数据 int uv_read_start(uv_stream_t* stream, uv_alloc_cb alloc_cb, uv_read_cb read_cb) { if (stream == NULL || alloc_cb == NULL || read_cb == NULL) return UV_EINVAL; // 流已经关闭,不能读 if (stream->flags & UV_HANDLE_CLOSING) return UV_EINVAL; // 流正在读 if (stream->flags & UV_HANDLE_READING) return UV_EALREADY; // 流不可读,说明可能是只写流 if (!(stream->flags & UV_HANDLE_READABLE)) return UV_ENOTCONN; return uv__read_start(stream, alloc_cb, read_cb); } int uv__read_start(uv_stream_t* stream, uv_alloc_cb alloc_cb, uv_read_cb read_cb) { assert(stream->type == UV_TCP || stream->type == UV_NAMED_PIPE || stream->type == UV_TTY); /* 设置标志位,表示此时流正在被使用读取 */ stream->flags |= UV_HANDLE_READING; stream->flags &= ~UV_HANDLE_READ_EOF; assert(uv__stream_fd(stream) >= 0); assert(alloc_cb); /* 注册回调函数 */ stream->read_cb = read_cb; stream->alloc_cb = alloc_cb; /* 启动io观察者 */ // 注册读事件 uv__io_start(stream->loop, &stream->io_watcher, POLLIN); // 激活 handle,有激活的 handle,事件循环不会退出 uv__handle_start(stream); uv__stream_osx_interrupt_select(stream); return 0; }
uv_read_stop
- 和 start 相反,start 是注册等待可读事件和打上正在读取这个标记,stop 就是撤销等待可读事件和清除这个标记
- 源码
int uv_read_stop(uv_stream_t* stream) { // 是否正在执行读取操作,如果不是,则没有必要停止 if (!(stream->flags & UV_HANDLE_READING)) return 0; // 清除正在读取的标记 stream->flags &= ~UV_HANDLE_READING; /* 停止流读取,关闭流io观察者 */ uv__io_stop(stream->loop, &stream->io_watcher, POLLIN); // 停掉 handle。允许事件循环退出 uv__handle_stop(stream); uv__stream_osx_interrupt_select(stream); /* 取消注册回调函数 */ stream->read_cb = NULL; stream->alloc_cb = NULL; return 0; }
- 判断流是否设置了可读属性
int uv_is_readable(const uv_stream_t* stream) { return !!(stream->flags & UV_HANDLE_READABLE); }
uv_write
-
源码
// 向stream handle 写入数据,实际上是调用uv_write2()这个函数 int uv_write(uv_write_t* req, // 往哪个流写 uv_stream_t* handle, // 一个写请求,记录了需要写入的数据和信息。数据来自下面的 const const uv_buf_t bufs[], // 个数 unsigned int nbufs, // 写完后执行的回调 uv_write_cb cb) { return uv_write2(req, handle, bufs, nbufs, NULL, cb); }
-
uv_write2
uv_write2 的主要逻辑就是封装一个写请求,插入到流的待写队列。然后根据 当前流的情况。看是直接写入还是等待会再写入
// 扩展的写函数,可用于在管道上发送数据 int uv_write2(uv_write_t* req, uv_stream_t* stream, const uv_buf_t bufs[], unsigned int nbufs, // 需要传递的文件描述符所在的流 uv_stream_t* send_handle, uv_write_cb cb) { int empty_queue; int err; err = uv__check_before_write(stream, nbufs, send_handle); if (err < 0) return err; // 流中缓存的数据大小是否为 0 empty_queue = (stream->write_queue_size == 0); // 初始化一个写请求 uv__req_init(stream->loop, req, UV_WRITE); // 写完后执行的回调 req->cb = cb; // 往哪个流写 req->handle = stream; // 写出错的错误码,初始化为 0 req->error = 0; req->send_handle = send_handle; QUEUE_INIT(&req->queue); // 默认 buf req->bufs = req->bufsml; // 不够则扩容 if (nbufs > ARRAY_SIZE(req->bufsml)) req->bufs = uv__malloc(nbufs * sizeof(bufs[0])); if (req->bufs == NULL) return UV_ENOMEM; // 把需要写入的数据填充到 req 中 memcpy(req->bufs, bufs, nbufs * sizeof(bufs[0])); // 需要写入的 buf 个数 req->nbufs = nbufs; // 目前写入的 buf 个数,初始化是 0 req->write_index = 0; // 更新流中待写数据的总长度,就是每个 buf 的数据大小加起来 stream->write_queue_size += uv__count_bufs(bufs, nbufs); // 插入待写队列 QUEUE_INSERT_TAIL(&stream->write_queue, &req->queue); /* stream->connect_req 非空说明是作为客户端,并且正在建立三次握手,建立成功 会置 connect_req 为 NULL。 这里非空说明还没有建立成功或者不是作为客户端(不是连接流)。即没有用到 connect_req 这个字段。 */ if (stream->connect_req) { /* Still connecting, do nothing. */ /* 仍在连接,什么也不做. */ } else if (empty_queue) { // 待写队列为空,则直接触发写动作,即操作文件描述符 uv__write(stream); } else { /* 阻塞流永远不会在队列中有任何东西。 */ assert(!(stream->flags & UV_HANDLE_BLOCKING_WRITES)); /* 队列非空,说明往底层写,uv__write 中不一样会注册等待可写事件,所以这 里注册一下 给流注册等待可写事件,触发的时候,把数据消费掉 */ uv__io_start(stream->loop, &stream->io_watcher, POLLOUT); uv__stream_osx_interrupt_select(stream); } return 0; }
-
uv__write
/* 当 io 观察者发现要写入数据的时候,它也会去将数据写入到底层,函数 uv__write() 会被调用,那什么时候才是可写呢, 回顾 stream handle 的成员变量,它有两个队列,当 stream->write_queue 队列存在数据时,表示可以写入,如果队列 为空则表示没有数据可以写 libuv的异步处理都是差不多的,都是通过io观察者去发现是否有可读可写,写数据的过程大致如下:用户将数据丢到写队列 中就直接返回了,io观察者发现队列有数据,stream handle 的处理 uv__stream_io()函数被调用,开始写入操作,这个写 入的操作是依赖系统的函数接口的,比如write()等,等写完了就通知用户即可 */ static void uv__write(uv_stream_t* stream) { QUEUE* q; uv_write_t* req; ssize_t n; /* 健壮性的处理,断言,确保存在stream handle的fd、队列存在等 */ assert(uv__stream_fd(stream) >= 0); for (;;) { // 待写队列为空,没得写 if (QUEUE_EMPTY(&stream->write_queue)) return; // 遍历待写队列,把每个节点的数据写入底层 q = QUEUE_HEAD(&stream->write_queue); req = QUEUE_DATA(q, uv_write_t, queue); assert(req->handle == stream); // 返回成功写入的个数 n = uv__try_write(stream, &(req->bufs[req->write_index]), req->nbufs - req->write_index, req->send_handle); if (n >= 0) { req->send_handle = NULL; // Returns 1 if all write request data has been written, or 0 if there is still more data to write if (uv__write_req_update(stream, req, n)) { // 写完了本请求的数据,做后续处理 uv__write_req_finish(req); return; /* TODO(bnoordhuis) Start trying to write the next request. */ } } else if (n != UV_EAGAIN) break; // 设置了一直写标记,则继续写 if (stream->flags & UV_HANDLE_BLOCKING_WRITES) continue; // 到这说明数据还没有完全被写入,注册等待可写事件,等待继续写 uv__io_start(stream->loop, &stream->io_watcher, POLLOUT); /* Notify select() thread about state change */ uv__stream_osx_interrupt_select(stream); return; } req->error = n; uv__write_req_finish(req); // 撤销等待可写事件 uv__io_stop(stream->loop, &stream->io_watcher, POLLOUT); uv__stream_osx_interrupt_select(stream); }
-
uv__write_req_finish
把节点从待写队列中移除。然后 插入写完成队列。最后把 io 观察者插入 pending 队列。在 pending 节点会知道 io 观察者的回调 (uv__stream_io)
static void uv__write_req_finish(uv_write_t* req) { uv_stream_t* stream = req->handle; // 移出队列 QUEUE_REMOVE(&req->queue); // 写入成功了 if (req->error == 0) { /* bufsml 是默认的 buf 数,如果不够,则 bufs 指向新的内存, 然后再储存数据。两者不等说明申请了额外的内存,需要 free 掉 */ if (req->bufs != req->bufsml) uv__free(req->bufs); req->bufs = NULL; } // 插入写完成队列 QUEUE_INSERT_TAIL(&stream->write_completed_queue, &req->queue); // 插入 pending 队列,在 pending 阶段执行回调 uv__io_feed(stream->loop, &stream->io_watcher); }
uv_shutdown
- 关闭流的写端就是相当于给流发送一个关闭请求,把请求挂载到流中,然后注册等待可写事件,在可写事件触发的时候就会执行关闭操作
- 源码
// 关闭流的写端口,它会等待未完成的写操作,在关闭后通过uv_shutdown_cb指定的回调函数告知应用层 int uv_shutdown(uv_shutdown_t* req, uv_stream_t* stream, uv_shutdown_cb cb) { /* 校验相关信息,只有 UV_TCP UV_TTY UV_NAMED_PIPE 类型的stream handle 才可以用*/ assert(stream->type == UV_TCP || stream->type == UV_TTY || stream->type == UV_NAMED_PIPE); // 流是可写的,并且还没关闭写端,也不是处于正在关闭状态 if (!(stream->flags & UV_HANDLE_WRITABLE) || stream->flags & UV_HANDLE_SHUT || stream->flags & UV_HANDLE_SHUTTING || uv__is_closing(stream)) { return UV_ENOTCONN; } assert(uv__stream_fd(stream) >= 0); /* 初始化请求 */ // 初始化一个关闭请求,关联的 handle 是 stream uv__req_init(stream->loop, req, UV_SHUTDOWN); req->handle = stream; // 关闭后执行的回调 req->cb = cb; stream->shutdown_req = req; stream->flags |= UV_HANDLE_SHUTTING; /* 设置关闭标志位 */ stream->flags &= ~UV_HANDLE_WRITABLE; // 注册等待可写事件 uv__io_start(stream->loop, &stream->io_watcher, POLLOUT); uv__stream_osx_interrupt_select(stream); return 0; }
uv__stream_close
- 关闭流
- 源码
void uv__stream_close(uv_stream_t* handle) { unsigned int i; uv__stream_queued_fds_t* queued_fds; #if defined(__APPLE__) /* Terminate select loop first */ if (handle->select != NULL) { uv__stream_select_t* s; s = handle->select; uv_sem_post(&s->close_sem); uv_sem_post(&s->async_sem); uv__stream_osx_interrupt_select(handle); uv_thread_join(&s->thread); uv_sem_destroy(&s->close_sem); uv_sem_destroy(&s->async_sem); uv__close(s->fake_fd); uv__close(s->int_fd); uv_close((uv_handle_t*) &s->async, uv__stream_osx_cb_close); handle->select = NULL; } #endif /* defined(__APPLE__) */ // 从事件循环中删除 io 观察者,移出 pending 队列 uv__io_close(handle->loop, &handle->io_watcher); // 停止读 uv_read_stop(handle); // 停掉 handle uv__handle_stop(handle); // 不可读、写 handle->flags &= ~(UV_HANDLE_READABLE | UV_HANDLE_WRITABLE); // 关闭非标准流的文件描述符 if (handle->io_watcher.fd != -1) { /* Don't close stdio file descriptors. Nothing good comes from it. */ if (handle->io_watcher.fd > STDERR_FILENO) uv__close(handle->io_watcher.fd); handle->io_watcher.fd = -1; } // 关闭通信 socket 对应的文件描述符 if (handle->accepted_fd != -1) { uv__close(handle->accepted_fd); handle->accepted_fd = -1; } /* Close all queued fds */ // 同上,这是在排队等待处理的通信 socket if (handle->queued_fds != NULL) { queued_fds = handle->queued_fds; for (i = 0; i < queued_fds->offset; i++) uv__close(queued_fds->fds[i]); uv__free(handle->queued_fds); handle->queued_fds = NULL; } assert(!uv__io_active(&handle->io_watcher, POLLIN | POLLOUT)); }
连接流
- 连接流是针对 tcp 的,连接即建立三次握手。所以我们首先介绍一下一些网络编程相关的内容。想要发起三次握手,首先我们先要有一个 socket。我们看 libuv 中如何新建一个 socket
- 源码
/* 1 获取一个新的 socket fd 2 把 fd 保存到 handle 里,并根据 flag 进行相关设置 3 绑定到本机随意的地址(如果设置了该标记的话) */ static int new_socket(uv_tcp_t* handle, int domain, unsigned long flags) { struct sockaddr_storage saddr; socklen_t slen; int sockfd; int err; // 获取一个 socket err = uv__socket(domain, SOCK_STREAM, 0); if (err < 0) return err; // 申请的 fd sockfd = err; // 设置选项和保存 socket 的文件描述符到 io 观察者中 err = uv__stream_open((uv_stream_t*) handle, sockfd, flags); if (err) { uv__close(sockfd); return err; } // 设置了需要绑定标记 UV_HANDLE_BOUND if (flags & UV_HANDLE_BOUND) { /* Bind this new socket to an arbitrary port */ slen = sizeof(saddr); memset(&saddr, 0, sizeof(saddr)); // 获取 fd 对应的 socket 信息,比如 ip,端口,可能没有 if (getsockname(uv__stream_fd(handle), (struct sockaddr*) &saddr, &slen)) { uv__close(sockfd); return UV__ERR(errno); } // 绑定到 socket 中,如果没有则绑定到系统随机选择的地址 if (bind(uv__stream_fd(handle), (struct sockaddr*) &saddr, slen)) { uv__close(sockfd); return UV__ERR(errno); } } return 0; }
- 上面的代码就是在 libuv 申请一个 socket 的逻辑,他还支持新建的 socket, 可以绑定到一个用户设置的,或者操作系统随机选择的地址。不过 libuv 并不 直接使用这个函数。而是又封装了一层
- 源码
/* 1 如果流还没有关联到 fd,则申请一个新的 fd 关联到流上。如果设置了绑定 标记,fd 还会和一个地址进行绑定。 2 如果流已经关联了一个 fd 1. 如果流设置了绑定地址的标记,但是已经通过 libuv 绑定了一个地址 (Libuv 会设置 UV_HANDLE_BOUND 标记,用户也可能是直接调 bind 函 数绑定了)。则不需要再次绑定,更新 flags 就行。 2. 如果流设置了绑定地址的标记,但是还没有通过 libuv 绑定一个地址, 这时候通过 getsocketname 判断用户是否自己通过 bind 函数绑定了一个地 址,是的话则不需要再次执行绑定操作。否则随机绑定到一个地址。 */ static int maybe_new_socket(uv_tcp_t* handle, int domain, unsigned long flags) { struct sockaddr_storage saddr; socklen_t slen; if (domain == AF_UNSPEC) { handle->flags |= flags; return 0; } // 已经有 socket fd 了 if (uv__stream_fd(handle) != -1) { // 该流需要绑定到一个地址 if (flags & UV_HANDLE_BOUND) { /* 流是否已经绑定到一个地址了。handle 的 flag 是在 new_socket 里设置的, 如果有这个标记说明已经执行过绑定了,直接更新 flags 就行。 */ if (handle->flags & UV_HANDLE_BOUND) { /* It is already bound to a port. */ handle->flags |= flags; return 0; } // 有 socket fd,但是可能还没绑定到一个地址 slen = sizeof(saddr); memset(&saddr, 0, sizeof(saddr)); // 获取 socket 绑定到的地址 if (getsockname(uv__stream_fd(handle), (struct sockaddr*) &saddr, &slen)) return UV__ERR(errno); // 绑定过了 socket 地址,则更新 flags 就行 if ((saddr.ss_family == AF_INET6 && ((struct sockaddr_in6*) &saddr)->sin6_port != 0) || (saddr.ss_family == AF_INET && ((struct sockaddr_in*) &saddr)->sin_port != 0)) { /* Handle is already bound to a port. */ handle->flags |= flags; return 0; } // 没绑定则绑定到随机地址,bind 中实现 if (bind(uv__stream_fd(handle), (struct sockaddr*) &saddr, slen)) return UV__ERR(errno); } handle->flags |= flags; return 0; } // 申请一个新的 fd 关联到流 return new_socket(handle, domain, flags); }
- 申请一个 socket 和给 socket 绑定一个地址。下面我们开看一下连接流的实现。
- 源码
// 1 申请一个 socket,绑定一个地址。 // 2 根据给定的服务器地址,发起三次握手,非阻塞的,会直接返回继续执行, 不会等到三次握手完成。 // 3 往流上挂载一个 connect 型的请求。 // 4 设置 io 观察者感兴趣的事件为可写。然后把 io 观察者插入事件循环的 io 观察者队列。等待可写的时候时候(完成三次握手),就会执行 cb 回调。 int uv__tcp_connect(uv_connect_t* req, uv_tcp_t* handle, const struct sockaddr* addr, unsigned int addrlen, uv_connect_cb cb) { int err; int r; assert(handle->type == UV_TCP); // 已经发起了 connect 了 if (handle->connect_req != NULL) return UV_EALREADY; /* FIXME(bnoordhuis) UV_EINVAL or maybe UV_EBUSY. */ if (handle->delayed_error != 0) goto out; // 申请一个 socket 和绑定一个地址,如果还没有的话 err = maybe_new_socket(handle, addr->sa_family, UV_HANDLE_READABLE | UV_HANDLE_WRITABLE); if (err) return err; do { // 清除全局错误变量的值 errno = 0; // 发起三次握手 r = connect(uv__stream_fd(handle), addr, addrlen); } while (r == -1 && errno == EINTR); // 三次握手还没有完成 if (r == -1 && errno != 0) { if (errno == EINPROGRESS) ; /* not an error */ else if (errno == ECONNREFUSED #if defined(__OpenBSD__) || errno == EINVAL #endif ) // 对方拒绝建立连接,延迟报错 handle->delayed_error = UV__ERR(ECONNREFUSED); else // 直接报错 return UV__ERR(errno); } out: // 初始化一个连接型 request,并设置某些字段 uv__req_init(handle->loop, req, UV_CONNECT); req->cb = cb; req->handle = (uv_stream_t*) handle; QUEUE_INIT(&req->queue); handle->connect_req = req; // 注册到 libuv 观察者队列 uv__io_start(handle->loop, &handle->io_watcher, POLLOUT); // 连接出错,插入 pending 队尾 if (handle->delayed_error) uv__io_feed(handle->loop, &handle->io_watcher); return 0; }
uv__tcp_listen
- 把流对的 fd 改成 listen 状 态,这样流就可以接收请求了。然后设置连接到来时执行的回调。最后注册 io 观察者到事件循环。等待连接到来。就会执行 uv__server_io。uv__server_io 再执行 connection_cb。监听流和其他流的一个区别是,当 io 观察者的事件触发时 , 监听流执行的回调是 uv__server_io 函数 。而其他流是在 uv__stream_io 里统一处理
- 源码
int uv__tcp_listen(uv_tcp_t* tcp, int backlog, uv_connection_cb cb) { static int single_accept_cached = -1; unsigned long flags; int single_accept; int err; if (tcp->delayed_error) return tcp->delayed_error; single_accept = uv__load_relaxed(&single_accept_cached); // 是否设置了不连续 accept。默认是连续 accept。 if (single_accept == -1) { const char* val = getenv("UV_TCP_SINGLE_ACCEPT"); single_accept = (val != NULL && atoi(val) != 0); /* Off by default. */ uv__store_relaxed(&single_accept_cached, single_accept); } // 设置不连续 accept if (single_accept) tcp->flags |= UV_HANDLE_TCP_SINGLE_ACCEPT; flags = 0; #if defined(__MVS__) flags |= UV_HANDLE_BOUND; #endif /* 可能还没有用于 listen 的 fd,socket 地址等。 这里申请一个 socket 和绑定到一个地址(如果调 listen 之前没有调 bind 则绑定 到随机地址) */ err = maybe_new_socket(tcp, AF_INET, flags); if (err) return err; // 设置 fd 为 listen 状态 if (listen(tcp->io_watcher.fd, backlog)) return UV__ERR(errno); // 建立连接后的业务回调 tcp->connection_cb = cb; tcp->flags |= UV_HANDLE_BOUND; // 有连接到来时的 libuv 层回调 tcp->io_watcher.cb = uv__server_io; // 注册读事件,等待连接到来 uv__io_start(tcp->loop, &tcp->io_watcher, POLLIN); return 0; }
uv__stream_io
-
有连接到来的时候,libuv 会执行 uv__server_io
-
源码
/* 1 调用 accept 摘下一个完成了三次握手的节点。 2 然后执行上层回调。上层回调会调用 uv_accept 消费 accept 返回的 fd。 然后再次注册等待可读事件(当然也可以不消费)。 3 如果 2 没有消费调 fd。则撤销等待可读事件,即处理完一个 fd 后,再 accept 下 一 个 。 如 果 2 中 消 费 了 fd 。 再 判 断 有 没 有 设 置 UV_HANDLE_TCP_SINGLE_ACCEPT 标记,如果有则休眠一会,分点给别的 进程 accept。否则继续 accept。 */ void uv__server_io(uv_loop_t* loop, uv__io_t* w, unsigned int events) { uv_stream_t* stream; int err; // 拿到 io 观察者所在的流 stream = container_of(w, uv_stream_t, io_watcher); assert(events & POLLIN); assert(stream->accepted_fd == -1); assert(!(stream->flags & UV_HANDLE_CLOSING)); // 继续注册事件,等待连接 uv__io_start(stream->loop, &stream->io_watcher, POLLIN); /* connection_cb can close the server socket while we're * in the loop so check it on each iteration. */ while (uv__stream_fd(stream) != -1) { assert(stream->accepted_fd == -1); #if defined(UV_HAVE_KQUEUE) if (w->rcount <= 0) return; #endif /* defined(UV_HAVE_KQUEUE) */ // 有连接到来,进行 accept err = uv__accept(uv__stream_fd(stream)); if (err < 0) { // 忽略出错处理 // accept 出错,触发回调 if (err == UV_EAGAIN || err == UV__ERR(EWOULDBLOCK)) return; /* Not an error. */ if (err == UV_ECONNABORTED) continue; /* Ignore. Nothing we can do about that. */ if (err == UV_EMFILE || err == UV_ENFILE) { err = uv__emfile_trick(loop, uv__stream_fd(stream)); if (err == UV_EAGAIN || err == UV__ERR(EWOULDBLOCK)) break; } stream->connection_cb(stream, err); continue; } UV_DEC_BACKLOG(w) // 保存通信 socket 对应的文件描述符 stream->accepted_fd = err; /* 有连接,执行上层回调,connection_cb 一般会调用 uv_accept 消费 accepted_fd。 然后重新注册等待可读事件 */ stream->connection_cb(stream, 0); /* 用户还没有消费 accept_fd。先解除 io 的事件, 等到用户调用 uv_accept 消费了 accepted_fd 再重新注册事件 */ if (stream->accepted_fd != -1) { /* The user hasn't yet accepted called uv_accept() */ uv__io_stop(loop, &stream->io_watcher, POLLIN); return; } // 定时睡眠一会(可被信号唤醒),分点给别的进程 accept if (stream->type == UV_TCP && (stream->flags & UV_HANDLE_TCP_SINGLE_ACCEPT)) { /* Give other processes a chance to accept connections. */ struct timespec timeout = { 0, 1 }; nanosleep(&timeout, NULL); } } }