Libuv源码分析 —— 10. 流

775 阅读16分钟

流【stream】

uv_stream_t --- 流句柄

  • 流句柄提供对双工通信通道的一种抽象。 uv_stream_t 是一个抽象类型,libuv提供了3种流的实现以 uv_tcp_t 、 uv_pipe_t 和 uv_tty_t 的形式。
数据类型
  • uv_stream_t

    流句柄类型。

  • uv_connect_t

    连接请求类型。

  • uv_shutdown_t

    停机请求类型。

  • uv_write_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* requv_stream_t* handleuv_shutdown_cb cb)

    停机双工流的向外(写)端。 它等待未处理的写请求完成。 handle 应该指向已初始化的流。 req 应该是一个未初始化的停机请求结构体。 cb 在停机完成后被调用。

  • int uv_listen(uv_stream_t* stream, int backloguv_connection_cb cb)

    开始侦听新来的连接。 backlog指内核可能排队的连接数,与 :man:listen(2) 相同。 当接受到新来的连接时,调用 uv_connection_cb 回调函数。

  • int uv_accept(uv_stream_t* serveruv_stream_t* client)

    调用用来配合 uv_listen() 接受新来的连接。 在接收到 uv_connection_cb 后调用这个函数以接受连接。 在调用这个函数前,客户端句柄必须被初始化。 < 0 返回值表示错误。

  • int uv_read_stop(uv_stream_t*)

    停止从流读取数据。 uv_read_cb 回调函数将不再被调用。

    这个函数是幂等的且可以在已停止的流上安全地被调用。

  • 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);
        }
      }
    }