这里只研究用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进行了区分,分为几种情况:
最后到了_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的参数中定义的