美文网首页程序员
Node 网络(tcp)编程笔记

Node 网络(tcp)编程笔记

作者: 淘皮渊 | 来源:发表于2018-04-27 18:09 被阅读0次

最近看 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 是用于监听连接而不是发起连接,其生命周期通常由创建,绑定,监听,连接,关闭组成的。

为了便于说明,把上面代码划分为四块:

1) new net.Server()
2) socket => console.log(socket)
3) server.listen()
4) () => 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的代码。

相关文章

  • Node 网络(tcp)编程笔记

    最近看 WebSocket,把涉及到的 tcp socket 这一层Node源代码梳理了一下有说的不对的地方,希望...

  • 【tcp】《TCP/IP网络编程》学习笔记

    《TCP/IP网络编程》学习笔记 https://github.com/riba2534/TCP-IP-Netwo...

  • 网络编程,TCP,UDP

    day26笔记【网络编程,TCP,UDP】 day26授课目录: 1_网络编程(网络编程概述)(了解) A:计算机...

  • 网络基础介绍

    网络编程的两种 TCP socket编程,是网络编程的主流。之所以叫Tcp socket编程,是因为底层是基于Tc...

  • socket 和 网络I/O模型

    《UNIX 网络编程卷一:套接字联网API》笔记 套接字 套接字编程接口,是在 TCP/IP 协议族中,应用层进入...

  • Python TCP编程

    Python网络编程之TCP 一、TCP协议 TCP协议,传输控制协议(Transmission Control ...

  • Linux网络编程篇之ICMP协议分析及ping程序实现

    Linux网络编程系列: Linux网络编程篇之Socket编程预备知识 Linux网络编程篇之TCP协议分析及聊...

  • socket通讯编程

    这一块属于网络编程,主要是学习TCP/IP四层的网络体系结构,学习TCP编程和UDP编程。 java.net中 一...

  • socket网络编程-基础知识

    什么是网络编程 网络编程的本质是两个设备之间的数据交换。 Socket、TCP/IP和Udp TCP 传输控制协议...

  • Node.js中的网络编程

    实验简介 此实验主要讲解TCP和UDP的网络编程,net模块提供了一个异步网络包装器,用于TCP网络编程,它包含了...

网友评论

    本文标题:Node 网络(tcp)编程笔记

    本文链接:https://www.haomeiwen.com/subject/mqsilftx.html