net.js代码泛读

net.js

这里只研究用net模块创建服务器的逻辑,也就是来自Node官网的这段背后的故事:

var net = require('net');

var server = net.createServer(function (socket) {
    socket.write('Echo server\r\n');
    socket.pipe(socket);
});

server.listen(1337, '127.0.0.1');

首先createServer函数其实是创建了一个Server的实例:

exports.createServer = function() {
    return new Server(arguments[0], arguments[1]);
};

第一个参数为创建服务器的选项,第二个为当有连接时的回调,如果只提供回调,那么使用默认选项,就像最上边的例子一样。

function Server(/* [ options, ] listener */) {
    if (!(this instanceof Server)) return new Server(arguments[0], arguments[1]);
    events.EventEmitter.call(this);

    var self = this;

    var options;

    // 判断提供了1个还是2个参数
    if (typeof arguments[0] == 'function') {
        options = {};
        self.on('connection', arguments[0]);
    } else {
        options = arguments[0] || {};

        if (typeof arguments[1] == 'function') {
            self.on('connection', arguments[1]);
        }
    }

    this._connections = 0;

    // 这段代码废弃掉了,定义了connections(连接数)的getter和setter
    Object.defineProperty(this, 'connections', {
        get: util.deprecate(function() {

            if (self._usingSlaves) {
                return null;
            }
            return self._connections;
        }, 'connections property is deprecated. Use getConnections() method'),
        set: util.deprecate(function(val) {
            return (self._connections = val);
        }, 'connections property is deprecated. Use getConnections() method'),
        configurable: true, enumerable: true
    });

    // 这个是底层C++应用的句柄,他执行的是真正的监听工作
    this._handle = null;
    this._usingSlaves = false;
    this._slaves = [];

    // 是否允许半开,默认是false
    this.allowHalfOpen = options.allowHalfOpen || false;
}

// 原型继承事件模块
util.inherits(Server, events.EventEmitter);

Server的构造函数基本就是一些实例属性的定义和对事件模块的继承,真正开始网络操作还要看Server的原型函数listen

Server.prototype.listen = function() {
    var self = this;

    // 最后一个参数如果是function,那么可以用来处理"开始监听"事件
    var lastArg = arguments[arguments.length - 1];
    if (typeof lastArg == 'function') {
        self.once('listening', lastArg);
    }

    // 第一个参数为端口
    var port = toNumber(arguments[0]);

    // The third optional argument is the backlog size.
    // When the ip is omitted it can be the second argument.

    // 第二个参数是ip,第三个是backlog,如果ip没提供,那么第二个参数就是backlog
    var backlog = toNumber(arguments[1]) || toNumber(arguments[2]);

    // 默认使用TCP的句柄
    var TCP = process.binding('tcp_wrap').TCP;

    // 如果没提供任何参数,或者只提供了一个函数型的参数用于处理开始监听,那么使用一个随机端口开始监听
    if (arguments.length == 0 || typeof arguments[0] == 'function') {
        // Bind to a random port.
        listen(self, '0.0.0.0', 0, null, backlog);

    // 如果第一个参数是TCP句柄,那么直接可以使用它的文件描述符开始监听了
    } else if (arguments[0] && typeof arguments[0] === 'object') {
        var h = arguments[0];
        if (h._handle) {
            h = h._handle;
        } else if (h.handle) {
            h = h.handle;
        }
        if (h instanceof TCP) {
            // 句柄存在,直接使用
            self._handle = h;
            listen(self, null, -1, -1, backlog);
        } else if (typeof h.fd === 'number' && h.fd >= 0) {
            // 如果文件描述符存在,通过fd获得socket的结构,开始监听
            listen(self, null, null, null, backlog, h.fd);
        } else {
            throw new Error('Invalid listen argument: ' + h);
        }

    // 如果第一个参数是字符串,那么监听一个命名管道
    } else if (isPipeName(arguments[0])) {
        // UNIX socket or Windows pipe.
        var pipeName = self._pipeName = arguments[0];
        listen(self, pipeName, -1, -1, backlog);

    // 没指定IP,但指定了指定的监听端口
    } else if (typeof arguments[1] == 'undefined' ||
        typeof arguments[1] == 'function' ||
        typeof arguments[1] == 'number') {
        // The first argument is the port, no IP given.
        listen(self, '0.0.0.0', port, 4, backlog);

    // 全部参数
    } else {
        // The first argument is the port, the second an IP.
        require('dns').lookup(arguments[1], function(err, ip, addressType) {
            if (err) {
                self.emit('error', err);
            } else {
                listen(self, ip || '0.0.0.0', port, ip ? addressType : 4, backlog);
            }
        });
    }
    return self;
};

这个函数判断了各种各样的情况,但最后都指向了另外一个非原型的listen函数:

function listen(self, address, port, addressType, backlog, fd) {
    if (!cluster) cluster = require('cluster');

    // 如果是master进程运行监听,直接启动
    if (cluster.isMaster) {
        // 调用真正执行监听的_listen2函数
        self._listen2(address, port, addressType, backlog, fd);
        return;
    }

    // 如果是worker进程,先通过地址端口等条件尝试找到一个句柄,如果找不到,那么创建一个
    cluster._getServer(self, address, port, addressType, fd, function(handle, err) {
        if (err) {
            self.emit('error', errnoException(err, 'bind'));
            return;
        }

        // 这里,如果监听的端口和句柄端口对不上,那么报绑定错误
        if (port && handle.getsockname && port != handle.getsockname().port) {
            self.emit('error', errnoException('EADDRINUSE', 'bind'));
            return;
        }

        // 给实例句柄赋值
        self._handle = handle;

        // 开始监听
        self._listen2(address, port, addressType, backlog, fd);
    });
}

在监听时对master和worker进行了区分,分为几种情况:

  1. 如果master进程执行了监听,那么worker要和master监听同样的资源。
  2. 如果master进程没执行监听,那么多个worker间要监听同样的资源。

最后到了_listen2函数:

Server.prototype._listen2 = function(address, port, addressType, backlog, fd) {
    debug('listen2', address, port, addressType, backlog);
    var self = this;
    var r = 0;

    // 如果句柄不存在,那么创建一个
    if (!self._handle) {
        debug('_listen2: create a handle');
        self._handle = createServerHandle(address, port, addressType, fd);
        if (!self._handle) {
            var error = errnoException(process._errno, 'listen');
            process.nextTick(function() {
                self.emit('error', error);
            });
            return;
        }
    } else {
        debug('_listen2: have a handle already');
    }

    // 连接后的回调 onconnection
    self._handle.onconnection = onconnection;
    self._handle.owner = self;

    // 句柄开始执行监听
    r = self._handle.listen(backlog || 511);

    // 错误处理
    if (r) {
        var ex = errnoException(process._errno, 'listen');
        self._handle.close();
        self._handle = null;
        process.nextTick(function() {
            self.emit('error', ex);
        });
        return;
    }

    // generate connection key, this should be unique to the connection
    this._connectionKey = addressType + ':' + address + ':' + port;

    // 监听成功,发送listening事件,处理函数在Server.prototype.listen的参数中定义的
    process.nextTick(function() {
        self.emit('listening');
    });
};

这段代码很好理解,因为具体逻辑都是底层代码做的,在Node层面,我们只关心连接回调是onconnection函数,有兴趣可以看看C++源码。

function onconnection(clientHandle) {
    var handle = this;
    var self = handle.owner;

    debug('onconnection');

    if (!clientHandle) {
        self.emit('error', errnoException(process._errno, 'accept'));
        return;
    }

    // 超过最大连接数的话直接断开
    if (self.maxConnections && self._connections >= self.maxConnections) {
        clientHandle.close();
        return;
    }

    // 初始化一个Socket的实例
    var socket = new Socket({
        handle: clientHandle,
        allowHalfOpen: self.allowHalfOpen
    });

    // 双工
    socket.readable = socket.writable = true;

    self._connections++;
    socket.server = self;

    DTRACE_NET_SERVER_CONNECTION(socket);
    COUNTER_NET_SERVER_CONNECTION(socket);

    // 发送connection事件,参数是socket实例
    self.emit('connection', socket);
}

经过上面的流程,当有连接时,发送connection事件,参数是socket实例本身,这个事件的监听函数是在net.createServer的参数中定义的

你可能感兴趣的:(node,net)