[Day 21] Node 注册事件 2

前言

昨天我们聊到, C++ 连接层中的 TCP 物件被 JS 调用, 拿来注册事件及回调函数, 今天让我们继续看下去。

C++ 连接层

https://github.com/nodejs/node/blob/master/src/tcp_wrap.cc

env->SetProtoMethod(t, "open", Open);
env->SetProtoMethod(t, "bind", Bind);
env->SetProtoMethod(t, "listen", Listen);
env->SetProtoMethod(t, "connect", Connect);
env->SetProtoMethod(t, "bind6", Bind6);
env->SetProtoMethod(t, "connect6", Connect6);
env->SetProtoMethod(t, "getsockname",
                    GetSockOrPeerName<TCPWrap, uv_tcp_getsockname>);
env->SetProtoMethod(t, "getpeername",
                    GetSockOrPeerName<TCPWrap, uv_tcp_getpeername>);
env->SetProtoMethod(t, "setNoDelay", SetNoDelay);
env->SetProtoMethod(t, "setKeepAlive", SetKeepAlive);

在 TCP 的初始化就可看到这段程序码, SetProtoMethod 在 V8 中的作用是给物件天加上 prototype , 用 JS 的说法就是给物件加上 method 。

我们随意看一个 Listen method 在干嘛

void TCPWrap::Listen(const FunctionCallbackInfo<Value>& args) {
  TCPWrap* wrap;
  ASSIGN_OR_RETURN_UNWRAP(&wrap,
                          args.Holder(),
                          args.GetReturnValue().Set(UV_EBADF));
  Environment* env = wrap->env();
  int backlog;
  if (!args[0]->Int32Value(env->context()).To(&backlog)) return;
  int err = uv_listen(reinterpret_cast<uv_stream_t*>(&wrap->handle_),
                      backlog,
                      OnConnection);
  args.GetReturnValue().Set(err);
}

跳过设定, 这段程序码的意思就是, 把回调函数 (这个方法的参数) 用 uv_listen 往下传。

所以看看 uv_listen 把参数(回调函数)传去哪了

int uv_listen(uv_stream_t* stream, int backlog, uv_connection_cb cb) {
  int err;

  switch (stream->type) {
  case UV_TCP:
    err = uv_tcp_listen((uv_tcp_t*)stream, backlog, cb);
    break;

  case UV_NAMED_PIPE:
    err = uv_pipe_listen((uv_pipe_t*)stream, backlog, cb);
    break;

  default:
    err = UV_EINVAL;
  }

  if (err == 0)
    uv__handle_start(stream);

  return err;
}

假设类型是 TCP 直接查看 uv_tcp_listen

有两个选择

  1. node/deps/uv/src/unix/tcp.c
  2. node/deps/uv/src/win/tcp.c

分别代表两个作业系统

我们只看 windows , 节省时间

int uv_tcp_listen(uv_tcp_t* handle, int backlog, uv_connection_cb cb) {
  unsigned int i, simultaneous_accepts;
  uv_tcp_accept_t* req;
  int err;

  assert(backlog > 0);

  if (handle->flags & UV_HANDLE_LISTENING) {
    handle->stream.serv.connection_cb = cb;
  }

  if (handle->flags & UV_HANDLE_READING) {
    return WSAEISCONN;
  }

  if (handle->delayed_error) {
    return handle->delayed_error;
  }

  if (!(handle->flags & UV_HANDLE_BOUND)) {
    err = uv_tcp_try_bind(handle,
                          (const struct sockaddr*) &uv_addr_ip4_any_,
                          sizeof(uv_addr_ip4_any_),
                          0);
    if (err)
      return err;
    if (handle->delayed_error)
      return handle->delayed_error;
  }

// 後略

略过检查与设定, 在uv_tcp_listen 中察看 uv_tcp_try_bind

static int uv_tcp_try_bind(uv_tcp_t* handle,
                           const struct sockaddr* addr,
                           unsigned int addrlen,
                           unsigned int flags) {
  DWORD err;
  int r;

  if (handle->socket == INVALID_SOCKET) {
    SOCKET sock;

    /* Cannot set IPv6-only mode on non-IPv6 socket. */
    if ((flags & UV_TCP_IPV6ONLY) && addr->sa_family != AF_INET6)
      return ERROR_INVALID_PARAMETER;

    sock = socket(addr->sa_family, SOCK_STREAM, 0);
    if (sock == INVALID_SOCKET) {
      return WSAGetLastError();
    }

    err = uv_tcp_set_socket(handle->loop, handle, sock, addr->sa_family, 0);
    if (err) {
      closesocket(sock);
      return err;
    }
  }
// 後略

略过创建 socket 直接查看 uv_tcp_set_socket

static int uv_tcp_set_socket(uv_loop_t* loop,
                             uv_tcp_t* handle,
                             SOCKET socket,
                             int family,
                             int imported) {
  DWORD yes = 1;
  int non_ifs_lsp;
  int err;

  if (handle->socket != INVALID_SOCKET)
    return UV_EBUSY;

  /* Set the socket to nonblocking mode */
  if (ioctlsocket(socket, FIONBIO, &yes) == SOCKET_ERROR) {
    return WSAGetLastError();
  }

  /* Make the socket non-inheritable */
  if (!SetHandleInformation((HANDLE) socket, HANDLE_FLAG_INHERIT, 0))
    return GetLastError();

  /* Associate it with the I/O completion port. Use uv_handle_t pointer as
   * completion key. */
  if (CreateIoCompletionPort((HANDLE)socket,
                             loop->iocp,
                             (ULONG_PTR)socket,
                             0) == NULL) {
    if (imported) {
      handle->flags |= UV_HANDLE_EMULATE_IOCP;
    } else {
      return GetLastError();
    }
  }
// 後略

略过设定 , 终於看到了一个熟悉的 method CreateIoCompletionPort

这就是我前几天聊的 IOCP 注册方法, 至此我们对其中一种事件的注册有了大概的了解。

总结

Node 注册事件的其中一种步骤

  1. 触发 JS 中引入的 C++ 连接层方法
  2. C++ 连接层会检查与打包 事件和回调函数
  3. C++ 连接层创建接收事件的 socket
  4. 引用 IOCP 方法把事件用非同步 IO 的方式进行注册, 且同时绑定事件发生後的回调函数。

( 据说 unix 中会使用 epoll 不过我对这件事没做太多确认 )

明天进度

明天开始介绍 libuv 如何 schedule 吧

明天见 !


<<:  用 Line LIFF APP 实现信箱验证绑定功能(1) - 取得 user email

>>:  07 | WordPress 空白间隔区块 Spacer Block

【Day24】维持权限 — 隐藏後门(一)

哈罗~ 来review一下, 之前提到维持权限时, 攻击者会建立後门或Rootkit, 并且会隐藏其...

Day04 - 随意玩之 AES-CBC 加/解密

加密前的资料在前几天我们都有拿到了!接着就是实作 AES-CBC 罗~ 流程如下图 关於 AES-C...

Swift 新手-iPhone 界面设计(二)

拇指法则 “拇指法则”是资深交互设计大神Steven Hoober在2013年对1300名手机用户的...

python的基本语法-回圈

我在学生时代的大魔王-回圈loop 曾经写了一个无限回圈,让电脑执行时当机XD loop以英文直接来...

LeetCode解题 Day21

485. Max Consecutive Ones https://leetcode.com/pro...