网络
uv_tcp_t --- TCP句柄
TCP句柄用于表示TCP流和服务器。
uv_tcp_t 是 uv_stream_t 的一个 '子类型' 。
数据类型
API
-
int
uv_tcp_init(uv_loop_t* loop, uv_tcp_t* handle)初始化句柄。 迄今为止没有创建套接字。
-
int
uv_tcp_init_ex(uv_loop_t* loop, uv_tcp_t* handle, unsigned int flags)以指定的标志来初始化句柄。 在此刻只有 flags 参数的低8位用于套接字域。一个套接字将为给定的域创建。 如果指定的域是
AF_UNSPEC则没有套接字被创建,就像uv_tcp_init()一样。 -
int
uv_tcp_open(uv_tcp_t* handle, uv_os_sock_t sock)打开一个已存在的文件描述符或者套接字作为一个TCP句柄。
-
int
uv_tcp_bind(uv_tcp_t* handle, const struct sockaddr* addr, unsigned int flags)绑定句柄到一个地址和端口。 addr 应该指向一个初始化了的
struct sockaddr_in或者struct sockaddr_in6。 -
int
uv_tcp_connect(uv_connect_t* req, uv_tcp_t* handle, const struct sockaddr* addr, uv_connect_cb cb)建立一个IPv4或IPv6的TCP连接。 提供一个已初始化的TCP句柄和一个未初始化的
uv_connect_t。 addr 应该指向一个已初始化的struct sockaddr_in或struct sockaddr_in6。
源码
uv_tcp_t
- 源码
struct uv_tcp_t { // 句柄[handle]相关参数 // uv_handle_t void* data; uv_loop_t* loop; uv_handle_type type; uv_close_cb close_cb; void* handle_queue[2]; union { int fd; void* reserved[4]; } u; uv_handle_t* next_closing; unsigned int flags; // 流相关参数 /* 等待写的字节数 */ \ size_t write_queue_size; \ /* 分配内存的函数 */ uv_alloc_cb alloc_cb; \ /* 读取完成时候执行的回调函数 */ uv_read_cb read_cb; /* 其实 uv_connect_t 是一个请求,从前面的文章我们也知道,在libuv存在 handle 与 request,很明显connect_req就是一个请求,它的作用就是请求建立连接,比如类似建立tcp连接 */ uv_connect_t *connect_req; \ /* uv_shutdown_t也是一个请求,它的作用与uv_connect_t刚好相反,关闭一个连接 */ uv_shutdown_t *shutdown_req; \ /* 抽象出来的io观察者 */ uv__io_t io_watcher; \ /* 写数据队列 */ void* write_queue[2]; \ /* 完成的写数据队列。 */ void* write_completed_queue[2]; \ /* 有新连接时的回调函数 */ uv_connection_cb connection_cb; \ /* 延时的错误代码 */ int delayed_error; \ /* 接受连接的描述符 fd */ int accepted_fd; \ /* fd 队列,可能有多个fd在排队 */ void* queued_fds; \ /* 目前为空 */ UV_STREAM_PRIVATE_PLATFORM_FIELDS }
uv_tcp_init
- 源码
// 初始化 uv_tcp_t 结构体 int uv_tcp_init(uv_loop_t* loop, uv_tcp_t* tcp) { // 未指定协议 return uv_tcp_init_ex(loop, tcp, AF_UNSPEC); } - uv_tcp_init_ex
// uv_tcp_init_ex 函数主要是申请一个 socket,然后把 socket 对应的文件描述 符和回调封装到 io 观察者中。但是还没有插入事件循环的观察者队列。 int uv_tcp_init_ex(uv_loop_t* loop, uv_tcp_t* tcp, unsigned int flags) { int domain; /* Use the lower 8 bits for the domain */ domain = flags & 0xFF; if (domain != AF_INET && domain != AF_INET6 && domain != AF_UNSPEC) return UV_EINVAL; if (flags & ~0xFF) return UV_EINVAL; // 初始化流部分 uv__stream_init(loop, (uv_stream_t*)tcp, UV_TCP); /* If anything fails beyond this point we need to remove the handle from * the handle queue, since it was added by uv__handle_init in uv_stream_init. */ if (domain != AF_UNSPEC) { int err = maybe_new_socket(tcp, domain, 0); if (err) { QUEUE_REMOVE(&tcp->handle_queue); return err; } } return 0; } - maybe_new_socket
// 如果流还没有对应的 fd,则申请一个新的,如果有则修改流的配置 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); }
uv__tcp_bind
- 源码
int uv__tcp_bind(uv_tcp_t* tcp, const struct sockaddr* addr, unsigned int addrlen, unsigned int flags) { int err; int on; /* Cannot set IPv6-only mode on non-IPv6 socket. */ if ((flags & UV_TCP_IPV6ONLY) && addr->sa_family != AF_INET6) return UV_EINVAL; // 设置流可读写 err = maybe_new_socket(tcp, addr->sa_family, 0); if (err) return err; // 设置 SO_REUSEADDR 属性 on = 1; if (setsockopt(tcp->io_watcher.fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) return UV__ERR(errno); #ifndef __OpenBSD__ #ifdef IPV6_V6ONLY if (addr->sa_family == AF_INET6) { on = (flags & UV_TCP_IPV6ONLY) != 0; if (setsockopt(tcp->io_watcher.fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof on) == -1) { #if defined(__MVS__) if (errno == EOPNOTSUPP) return UV_EINVAL; #endif return UV__ERR(errno); } } #endif #endif errno = 0; // 绑定地址到 socket if (bind(tcp->io_watcher.fd, addr, addrlen) && errno != EADDRINUSE) { if (errno == EAFNOSUPPORT) /* OSX, other BSDs and SunoS fail with EAFNOSUPPORT when binding a * socket created with AF_INET to an AF_INET6 address or vice versa. */ return UV_EINVAL; return UV__ERR(errno); } // 设置已经绑定标记 tcp->delayed_error = UV__ERR(errno); tcp->flags |= UV_HANDLE_BOUND; if (addr->sa_family == AF_INET6) tcp->flags |= UV_HANDLE_IPV6; return 0; }
uv_tcp_listen
- 执行 uv_tcp_listen 之后,服务器已经启动,如果这时候有一个连接到来(已完成三次握手)。就会执行 uv__server_io
- 源码
/* 1 设置 socket 为监听状态,即可以等待建立连接 2 封装 io 观察者,然后注册到事件循环 3 保存相关上下文,比如业务回调函数 */ 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,否则就是连续 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; // 注册 io 观察者到事件循环的 io 观察者队列,设置等待读事件 uv__io_start(tcp->loop, &tcp->io_watcher, POLLIN); return 0; }
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); } } } - 当有连接到来,libuv 会调用 accept 摘下一个连接,然后通知用户,并且停止 accept。等到用户消费 accepted_fd。用户可以通过 uv_accept 消费 accepted_fd。比如
uv_tcp_t *client = (uv_tcp_t*) malloc(sizeof(uv_tcp_t)); uv_tcp_init(loop, client); uv_accept(server, (uv_stream_t*) client); - 首先申请一个新的 uv_tcp_t 结构体,该新的结构体用来表示和客户端通信,之前的那个是用于处理连接,新的用来处理通信
uv_accept
- 源码
// 配合 uv_listen() 接受新来的连接 // 在调用这个函数前,客户端句柄必须被初始化 int uv_accept(uv_stream_t* server, uv_stream_t* client) { int err; assert(server->loop == client->loop); if (server->accepted_fd == -1) return UV_EAGAIN; switch (client->type) { case UV_NAMED_PIPE: case UV_TCP: /* 获取文件描述符,并赋值给 client->io_watcher.fd */ err = uv__stream_open(client, server->accepted_fd, UV_HANDLE_READABLE | UV_HANDLE_WRITABLE); if (err) { /* 出现错误就关闭 */ uv__close(server->accepted_fd); goto done; } break; case UV_UDP: /* 对于udp协议,通过uv_udp_open去获取文件描述符 */ err = uv_udp_open((uv_udp_t*) client, server->accepted_fd); if (err) { uv__close(server->accepted_fd); goto done; } break; default: return UV_EINVAL; } client->flags |= UV_HANDLE_BOUND; done: /* 处理在排队的连接请求 */ if (server->queued_fds != NULL) { uv__stream_queued_fds_t* queued_fds; queued_fds = server->queued_fds; /* 处理第一个排队的 */ server->accepted_fd = queued_fds->fds[0]; /* All read, free */ assert(queued_fds->offset > 0); if (--queued_fds->offset == 0) { uv__free(queued_fds); server->queued_fds = NULL; } else { /* Shift rest */ memmove(queued_fds->fds, queued_fds->fds + 1, queued_fds->offset * sizeof(*queued_fds->fds)); } } else { // 消费完了,恢复为-1 server->accepted_fd = -1; if (err == 0) // 重新注册时间,处理下一个连接,有的话 uv__io_start(server->loop, &server->io_watcher, POLLIN); } return err; } - libuv 作为客户端的过程也是类似的,首先申请一个 socket,然后把 socket 对应的 fd 和回调封装成 io 观察者,然后注册到 libuv,等三次握手成功后,会执行回调函数。