Libuv源码分析 —— 11. 网络

718 阅读7分钟

网络

uv_tcp_t --- TCP句柄

TCP句柄用于表示TCP流和服务器。

uv_tcp_t 是 uv_stream_t 的一个 '子类型' 。

数据类型
  • uv_tcp_t

    TCP句柄类型。

API
  • int uv_tcp_init(uv_loop_t* loopuv_tcp_t* handle)

    初始化句柄。 迄今为止没有创建套接字。

  • int uv_tcp_init_ex(uv_loop_t* loopuv_tcp_t* handle, unsigned int flags)

    以指定的标志来初始化句柄。 在此刻只有 flags 参数的低8位用于套接字域。一个套接字将为给定的域创建。 如果指定的域是 AF_UNSPEC 则没有套接字被创建,就像 uv_tcp_init() 一样。

  • int uv_tcp_open(uv_tcp_t* handleuv_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* requv_tcp_t* handle, const struct sockaddr* addruv_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,等三次握手成功后,会执行回调函数。