昨天我們聊到, C++ 連接層中的 TCP 物件被 JS 調用, 拿來註冊事件及回調函數, 今天讓我們繼續看下去。
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
有兩個選擇
分別代表兩個作業系統
我們只看 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 註冊事件的其中一種步驟
( 據說 unix 中會使用 epoll 不過我對這件事沒做太多確認 )
明天開始介紹 libuv 如何 schedule 吧
明天見 !