最近看 webSocket, 把涉及到的 tcp socket 这一层 Node 源代码梳理了一下
有说的不对的地方, 希望大家指正
代码
Node 版本: 8.9.4
- const server = new net.Server((socket) => {
- console.log(socket);
- });
- server.listen('1234', () => {
- console.log('server started at 1234');
- });
- Socket
服务器 socket 是用于监听连接而不是发起连接, 其生命周期通常由创建, 绑定, 监听, 连接, 关闭组成的.
为了便于说明, 把上面代码划分为四块:
- ) new net.Server()
- ) socket => console.log(socket)
- ) server.listen()
- ) () => console.log('server started at 1234')
服务器启动流程
运行代码, 我们可以通过 lsof 看到一个 socket 处于 LISTEN 状态, 这就是服务器 socket.
$ lsof -i tcp:1234
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
node 64852 i312714 14u IPv6 0xe781f0e6b2c52e5b 0t0 TCP *:search-agent (LISTEN)
在代码块 1 我们初始化了 net.Server(), 具体代码在 net.js L1176, 这里为了精简文章, 只贴出了关键代码, 后面都会只贴出关键代码.
- function Server(options, connectionListener) {
- this.on('connection', connectionListener);
- this._connections = 0;
- this[async_id_symbol] = -1;
- this._handle = null;
- this._usingWorkers = false;
- this._workers = [];
- this._unref = false;
- this.allowHalfOpen = options.allowHalfOpen || false;
- this.pauseOnConnect = !!options.pauseOnConnect;
- }
- util.inherits(Server, EventEmitter);
这里先是注册了 connection 的事件监听回调 connectionListener, 也就是代码块 2
然后初始化了 _handle, _usingWorkers, _workers, _unref 等属性
并且让 Server 继承 EventEmitter
服务器监听
按照代码运行顺序, 下一步是执行代码块 3, 调用了 server.listen, 源代码代码在 net.js L1411
- Server.prototype.listen = function(...args) {
- var normalized = normalizeArgs(args);
- var options = normalized[0];
- var cb = normalized[1];
- var backlog;
- if (typeof options.port === 'number' || typeof options.port === 'string') {
- if (!isLegalPort(options.port)) {
- throw new ERR_SOCKET_BAD_PORT(options.port);
- }
- backlog = options.backlog || backlogFromArgs;
- // start TCP server listening on host:port
- if (options.host) {
- lookupAndListen(this, options.port | 0, options.host, backlog,options.exclusive);
- } else { // Undefined host, listens on unspecified address
- // Default addressType 4 will be used to search for master server
- listenInCluster(this, null, options.port | 0, 4,backlog, undefined, options.exclusive);
- }
- return this;
- }
- };
省略中间代码追述, 调用了 setupListenHandle
- function setupListenHandle(address, port, addressType, backlog, fd) {
- rval = createServerHandle(address, port, addressType, fd);
- this._handle = rval;
- this[async_id_symbol] = getNewAsyncId(this._handle);
- this._handle.onconnection = onconnection;
- this._handle.owner = this;
- this._handle.listen(backlog || 511);
- }
关键代码第一步是 createServerHandle, 源代码如下
- function createServerHandle(address, port, addressType, fd) {
- handle = new TCP(TCPConstants.SERVER);
- isTCP = true;
- err = handle.bind(address, port);
- return handle;
- }
重点来了:
TCP 是从 tcp_wrap 模块中引入的, tcp_wrap 是一个 C++ 写的 Node 模块, TCPWrap 的继承链是 TCPWrap <ConnectionWrap <LibuvStreamWrap <HandleWrap, StreamBase < HandleWrap
在 tcp_wrap 的 Initialize 中定义了 TCP 的 open, bind, listen 等方法
createServerHandle 中的 new TCP() 是调用 tcp_wrap TCPWrap::New, 最后会返回一个 TCPWrap 的实例, 这里主要是初始化了 libuv 中 uv_handle_t
handle.bind() 调用 TCPWrap::Bind, 这里调用了 libuv 中 uv__tcp_bind 这里给之前初始化的 handle 设置了 socket, 并且绑定了 host,port 等信息
这时候在 js 中访问 handle.fd 就会返回真实的文件描述符, 应该与 lsof 中看到的 FD 一致的
回到 setupListenHandle 中
handle.onconnection = onconnection
这个稍后解释
handle.listen() 调用了 TCPWrap::Listen, 这里调用了 libuv 中的 uv_listen(uv_tcp_listen), 最终调用系统的 listen 方法, 并且注册了 uv__server_io 回调
以上完成了 Socket 的创建, 绑定和监听.
服务器连接
服务器接收连接之后就会触发代码块 2, 在代码块 2 中就可以拿到连接 socket, 从中可以读取数据或者写入数据.
在 new net.Server() 时, 这个回调作为 connection 事件绑定到 server 上.
在创建服务器 socket 的时候,
this._handle.onconnection = onconnection
这个 onconnection 就会最终 emit connection 事件.
- function onconnection(err, clientHandle) {
- var handle = this;
- var self = handle.owner;
- var socket = new Socket({
- handle: clientHandle,
- allowHalfOpen: self.allowHalfOpen,
- pauseOnCreate: self.pauseOnConnect
- });
- socket.readable = socket.writable = true;
- self._connections++;
- socket.server = self;
- socket._server = self;
- self.emit('connection', socket);
- }
回到调用 uv_listen 的时候的代码
- int err = uv_listen(reinterpret_cast<uv_stream_t*>(&wrap->handle_),
- backlog,
- OnConnection);
这里的 OnConnection 其实是 ConnectionWrap<WrapType, UVType>::OnConnection 这个方法, 这个方法里面会调用 js 的 onconnection 回调.
OnConnection 这个方法里面 生成了新的 socket 并且赋值给了 argv 最终传递给了 onconnection
- template <typename WrapType, typename UVType>
- void ConnectionWrap<WrapType, UVType>::OnConnection(uv_stream_t* handle,
- int status) {
- ...
- ...
- if (status == 0) {
- // Instantiate the client javascript object and handle.
- Local<Object> client_obj = WrapType::Instantiate(env,
- wrap_data,
- WrapType::SOCKET);
- WrapType* wrap;
- ASSIGN_OR_RETURN_UNWRAP(&wrap, client_obj);
- uv_stream_t* client_handle =
- reinterpret_cast<uv_stream_t*>(&wrap->handle_);
- if (uv_accept(handle, client_handle))
- return;
- argv[1] = client_obj;
- }
- // connection_string 就是 "onconnection"
- wrap_data->MakeCallback(env->onconnection_string(), arraysize(argv), argv);
- }
到这里基本说明了 Node 在连接时候的代码逻辑. 后面也会继续整理一下关于 socket close 的代码.
来源: http://www.jianshu.com/p/ebba432470f5