Server.prototype.listen = function(...args) {
var normalized = normalizeArgs(args);
var options = normalized[0];
var cb = normalized[1];
if (this._handle) {
throw new ERR_SERVER_ALREADY_LISTEN();
}
var hasCallback = (cb !== null);
if (hasCallback) {
this.once('listening', cb);
}
var backlogFromArgs =
// (handle, backlog) or (path, backlog) or (port, backlog)
toNumber(args.length > 1 && args[1]) ||
toNumber(args.length > 2 && args[2]); // (port, host, backlog)
options = options._handle || options.handle || options;
// (handle[, backlog][, cb]) where handle is an object with a handle
if (options instanceof TCP) {
this._handle = options;
this[async_id_symbol] = this._handle.getAsyncId();
listenInCluster(this, null, -1, -1, backlogFromArgs);
return this;
}
// (handle[, backlog][, cb]) where handle is an object with a fd
if (typeof options.fd === 'number' && options.fd >= 0) {
listenInCluster(this, null, null, null, backlogFromArgs, options.fd);
return this;
}
// ([port][, host][, backlog][, cb]) where port is omitted,
// that is, listen(), listen(null), listen(cb), or listen(null, cb)
// or (options[, cb]) where options.port is explicitly set as undefined or
// null, bind to an arbitrary unused port
if (args.length === 0 || typeof args[0] === 'function' ||
(typeof options.port === 'undefined' && 'port' in options) ||
options.port === null) {
options.port = 0;
}
// ([port][, host][, backlog][, cb]) where port is specified
// or (options[, cb]) where options.port is specified
// or if options.port is normalized as 0 before
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;
}
// (path[, backlog][, cb]) or (options[, cb])
// where path or options.path is a UNIX domain socket or Windows pipe
if (options.path && isPipeName(options.path)) {
var pipeName = this._pipeName = options.path;
backlog = options.backlog || backlogFromArgs;
listenInCluster(this, pipeName, -1, -1,
backlog, undefined, options.exclusive);
return this;
}
throw new ERR_INVALID_OPT_VALUE('options', util.inspect(options));
};
function setupListenHandle(address, port, addressType, backlog, fd) {
debug('setupListenHandle', address, port, addressType, backlog, fd);
// If there is not yet a handle, we need to create one and bind.
// In the case of a server sent via IPC, we don't need to do this.
if (this._handle) {
debug('setupListenHandle: have a handle already');
} else {
debug('setupListenHandle: create a handle');
var rval = null;
// Try to bind to the unspecified IPv6 address, see if IPv6 is available
if (!address && typeof fd !== 'number') {
rval = createServerHandle('::', port, 6, fd);
if (typeof rval === 'number') {
rval = null;
address = '0.0.0.0';
addressType = 4;
} else {
address = '::';
addressType = 6;
}
}
if (rval === null)
rval = createServerHandle(address, port, addressType, fd);
if (typeof rval === 'number') {
var error = exceptionWithHostPort(rval, 'listen', address, port);
process.nextTick(emitErrorNT, this, error);
return;
}
this._handle = rval;
}
this[async_id_symbol] = getNewAsyncId(this._handle);
this._handle.onconnection = onconnection;
this._handle.owner = this;
// Use a backlog of 512 entries. We pass 511 to the listen() call because
// the kernel does: backlogsize = roundup_pow_of_two(backlogsize + 1);
// which will thus give us a backlog of 512 entries.
var err = this._handle.listen(backlog || 511);
}
TCPWrap::TCPWrap(Environment* env, Local<Object> object, ProviderType provider)
: ConnectionWrap(env, object, provider) {
int r = uv_tcp_init(env->event_loop(), &handle_);
CHECK_EQ(r, 0); // How do we proxy this error up to javascript?
// Suggestion: uv_tcp_init() returns void.
}
这里调用了uv_tcp_init方法这里不仔细介绍了,其实就是创建了socket并bind。
server handle的listen方法,也就是上述创建的TCP对象的listen方法,其代码如下:
function connect(...args) {
var normalized = normalizeArgs(args);
var options = normalized[0];
debug('createConnection', normalized);
var socket = new Socket(options);
if (options.timeout) {
socket.setTimeout(options.timeout);
}
return Socket.prototype.connect.call(socket, normalized);
}
这里实例化了一个Socket,并调用了原型connect方法。
我们接下来看一下Socket的构造函数和connect方法:
function Socket(options) {
if (!(this instanceof Socket)) return new Socket(options);
this.connecting = false;
// Problem with this is that users can supply their own handle, that may not
// have _handle.getAsyncId(). In this case an[async_id_symbol] should
// probably be supplied by async_hooks.
this[async_id_symbol] = -1;
this._hadError = false;
this._handle = null;
this._parent = null;
this._host = null;
this[kLastWriteQueueSize] = 0;
this[kTimeout] = null;
if (typeof options === 'number')
options = { fd: options }; // Legacy interface.
else
options = util._extend({}, options);
options.readable = options.readable || false;
options.writable = options.writable || false;
const allowHalfOpen = options.allowHalfOpen;
// Prevent the "no-half-open enforcer" from being inherited from `Duplex`.
options.allowHalfOpen = true;
// For backwards compat do not emit close on destroy.
options.emitClose = false;
stream.Duplex.call(this, options);
// Default to *not* allowing half open sockets.
this.allowHalfOpen = Boolean(allowHalfOpen);
if (options.handle) {
this._handle = options.handle; // private
this[async_id_symbol] = getNewAsyncId(this._handle);
} else if (options.fd !== undefined) {
const fd = options.fd;
this._handle = createHandle(fd, false);
this._handle.open(fd);
this[async_id_symbol] = this._handle.getAsyncId();
// options.fd can be string (since it is user-defined),
// so changing this to === would be semver-major
// See: https://github.com/nodejs/node/pull/11513
// eslint-disable-next-line eqeqeq
if ((fd == 1 || fd == 2) &&
(this._handle instanceof Pipe) &&
process.platform === 'win32') {
// Make stdout and stderr blocking on Windows
var err = this._handle.setBlocking(true);
if (err)
throw errnoException(err, 'setBlocking');
this._writev = null;
this._write = makeSyncWrite(fd);
// makeSyncWrite adjusts this value like the original handle would, so
// we need to let it do that by turning it into a writable, own property.
Object.defineProperty(this._handle, 'bytesWritten', {
value: 0, writable: true
});
}
}
// shut down the socket when we're finished with it.
this.on('end', onReadableStreamEnd);
initSocketHandle(this);
this._pendingData = null;
this._pendingEncoding = '';
// handle strings directly
this._writableState.decodeStrings = false;
// if we have a handle, then start the flow of data into the
// buffer. if not, then this will happen when we connect
if (this._handle && options.readable !== false) {
if (options.pauseOnCreate) {
// stop the handle from reading and pause the stream
this._handle.reading = false;
this._handle.readStop();
this.readableFlowing = false;
} else if (!options.manualStart) {
this.read(0);
}
}
// Reserve properties
this.server = null;
this._server = null;
// Used after `.destroy()`
this[kBytesRead] = 0;
this[kBytesWritten] = 0;
}
util.inherits(Socket, stream.Duplex);
int uv__tcp_connect(uv_connect_t* req,
uv_tcp_t* handle,
const struct sockaddr* addr,
unsigned int addrlen,
uv_connect_cb cb) {
int err;
int r;
assert(handle->type == UV_TCP);
if (handle->connect_req != NULL)
return UV_EALREADY; /* FIXME(bnoordhuis) UV_EINVAL or maybe UV_EBUSY. */
err = maybe_new_socket(handle,
addr->sa_family,
UV_STREAM_READABLE | UV_STREAM_WRITABLE);
if (err)
return err;
handle->delayed_error = 0;
do {
errno = 0;
r = connect(uv__stream_fd(handle), addr, addrlen);
} while (r == -1 && errno == EINTR);
/* We not only check the return value, but also check the errno != 0.
* Because in rare cases connect() will return -1 but the errno
* is 0 (for example, on Android 4.3, OnePlus phone A0001_12_150227)
* and actually the tcp three-way handshake is completed.
*/
if (r == -1 && errno != 0) {
if (errno == EINPROGRESS)
; /* not an error */
else if (errno == ECONNREFUSED)
/* If we get a ECONNREFUSED wait until the next tick to report the
* error. Solaris wants to report immediately--other unixes want to
* wait.
*/
handle->delayed_error = UV__ERR(errno);
else
return UV__ERR(errno);
}
uv__req_init(handle->loop, req, UV_CONNECT);
req->cb = cb;
req->handle = (uv_stream_t*) handle;
QUEUE_INIT(&req->queue);
handle->connect_req = req;
uv__io_start(handle->loop, &handle->io_watcher, POLLOUT);
if (handle->delayed_error)
uv__io_feed(handle->loop, &handle->io_watcher);
return 0;
}
function afterConnect(status, handle, req, readable, writable) {
var self = handle.owner;
// callback may come after call to destroy
if (self.destroyed) {
return;
}
// Update handle if it was wrapped
// TODO(indutny): assert that the handle is actually an ancestor of old one
handle = self._handle;
debug('afterConnect');
assert(self.connecting);
self.connecting = false;
self._sockname = null;
if (status === 0) {
self.readable = readable;
self.writable = writable;
self._unrefTimer();
self.emit('connect');
self.emit('ready');
// start the first read, or get an immediate EOF.
// this doesn't actually consume any bytes, because len=0.
if (readable && !self.isPaused())
self.read(0);
} else {
self.connecting = false;
var details;
if (req.localAddress && req.localPort) {
details = req.localAddress + ':' + req.localPort;
}
var ex = exceptionWithHostPort(status,
'connect',
req.address,
req.port,
details);
if (details) {
ex.localAddress = req.localAddress;
ex.localPort = req.localPort;
}
self.destroy(ex);
}
}
// This function is called whenever the handle gets a
// buffer, or when there's an error reading.
function onread(nread, buffer) {
var handle = this;
var self = handle.owner;
assert(handle === self._handle, 'handle != self._handle');
self._unrefTimer();
debug('onread', nread);
if (nread > 0) {
debug('got data');
// read success.
// In theory (and in practice) calling readStop right now
// will prevent this from being called again until _read() gets
// called again.
// Optimization: emit the original buffer with end points
var ret = self.push(buffer);
if (handle.reading && !ret) {
handle.reading = false;
debug('readStop');
var err = handle.readStop();
if (err)
self.destroy(errnoException(err, 'read'));
}
return;
}
// if we didn't get any bytes, that doesn't necessarily mean EOF.
// wait for the next one.
if (nread === 0) {
debug('not any data, keep waiting');
return;
}
// Error, possibly EOF.
if (nread !== UV_EOF) {
return self.destroy(errnoException(nread, 'read'));
}
debug('EOF');
// push a null to signal the end of data.
// Do it before `maybeDestroy` for correct order of events:
// `end` -> `close`
self.push(null);
self.read(0);
}
// Manually shove something into the read() buffer.
// This returns true if the highWaterMark has not been hit yet,
// similar to how Writable.write() returns true if you should
// write() some more.
Readable.prototype.push = function(chunk, encoding) {
var state = this._readableState;
var skipChunkCheck;
if (!state.objectMode) {
if (typeof chunk === 'string') {
encoding = encoding || state.defaultEncoding;
if (encoding !== state.encoding) {
chunk = Buffer.from(chunk, encoding);
encoding = '';
}
skipChunkCheck = true;
}
} else {
skipChunkCheck = true;
}
return readableAddChunk(this, chunk, encoding, false, skipChunkCheck);
};
这里主要调用了readableAddChunk方法,代码如下:
function readableAddChunk(stream, chunk, encoding, addToFront, skipChunkCheck) {
debug('readableAddChunk', chunk);
var state = stream._readableState;
if (chunk === null) {
state.reading = false;
onEofChunk(stream, state);
} else {
var er;
if (!skipChunkCheck)
er = chunkInvalid(state, chunk);
if (er) {
stream.emit('error', er);
} else if (state.objectMode || chunk && chunk.length > 0) {
if (typeof chunk !== 'string' &&
!state.objectMode &&
Object.getPrototypeOf(chunk) !== Buffer.prototype) {
chunk = Stream._uint8ArrayToBuffer(chunk);
}
if (addToFront) {
if (state.endEmitted)
stream.emit('error', new ERR_STREAM_UNSHIFT_AFTER_END_EVENT());
else
addChunk(stream, state, chunk, true);
} else if (state.ended) {
stream.emit('error', new ERR_STREAM_PUSH_AFTER_EOF());
} else if (state.destroyed) {
return false;
} else {
state.reading = false;
if (state.decoder && !encoding) {
chunk = state.decoder.write(chunk);
if (state.objectMode || chunk.length !== 0)
addChunk(stream, state, chunk, false);
else
maybeReadMore(stream, state);
} else {
addChunk(stream, state, chunk, false);
}
}
} else if (!addToFront) {
state.reading = false;
maybeReadMore(stream, state);
}
}
return needMoreData(state);
}
function addChunk(stream, state, chunk, addToFront) {
if (state.flowing && state.length === 0 && !state.sync) {
state.awaitDrain = 0;
stream.emit('data', chunk);
} else {
// update the buffer info.
state.length += state.objectMode ? 1 : chunk.length;
if (addToFront)
state.buffer.unshift(chunk);
else
state.buffer.push(chunk);
if (state.needReadable)
emitReadable(stream);
}
maybeReadMore(stream, state);
}
function addChunk(stream, state, chunk, addToFront) {
if (state.flowing && state.length === 0 && !state.sync) {
state.awaitDrain = 0;
stream.emit('data', chunk);
} else {
// update the buffer info.
state.length += state.objectMode ? 1 : chunk.length;
if (addToFront)
state.buffer.unshift(chunk);
else
state.buffer.push(chunk);
if (state.needReadable)
emitReadable(stream);
}
maybeReadMore(stream, state);
}
有了前面介绍的事件循环等章节,相信大家再看Node.js中其他模块的实现就会轻松许多。本章将带着大家过一遍net有关的实现。主要包括
net.createServer
、net.createConnection
、 connect事件、 data事件。net.createServer
我们在使用过程中,通常情况下以如下方式使用:
这里
net.createServer
之后调用server.listen
,这面我们就重点讲解这两个函数。net.createServer
net.createServer([options][, connectionListener])
用来创建一个新的TCP或IPC服务,其入口地址在./lib/net.js
,代码如下:我们下面来关注
Server
的构造函数:这里我们看到其实是监听了
connection
事件。server.listen
server.listen
方法代码如下:这里使用
listenInCluster
方法用来监听connection
并触发connection
事件,我们省略掉几个调用链,发现最终其实调用的是setupListenHandle
方法,其代码如下:这里我们截取了关键代码,其中主要做了两件事(以TCP为例):
在
createServerHandle
方法中实例化了TCP对象,TCP定义在./src/tcp_wrap.cc
中,我们直接进去看下:这里调用了
uv_tcp_init
方法这里不仔细介绍了,其实就是创建了socket
并bind
。server handle
的listen
方法,也就是上述创建的TCP对象的listen
方法,其代码如下:最终调用了
uv_listen
进行监听,uv_listen
其实见识调用了uv__io_start
,是的i/o在event loop的poll阶段进行处理。当接收到连接后,调用TCP对象的
onconnection
属性对应的方法,也就是net.js
中的onconnection
方法,代码如下:上述代码其实就是触发了
connection
事件。net.createConnection
net.createConnection
用于创建net.Socket
的工厂函数,立即使用socket.connect()
初始化链接,然后返回启动连接的net.Socket
。我们直奔
./lib/net.js
中net.createConnection
的代码,其实是connect
方法:这里实例化了一个
Socket
,并调用了原型connect
方法。我们接下来看一下
Socket
的构造函数和connect
方法:这里我们看到
Socket
寄生组合继承了stream.Duplex
,同时给_handle
属性赋值为TCP实例(createHandle上述介绍过,创建并返回TCP实例),最后initSocketHandle
初始化了TCP实例。看完了
Socket
的构造函数,我们再来看connect
方法,connect
方法最终调用的是internalConnect
方法,代码如下:这里我们看到其调用了
self._handle.connect()
,也就是TCP对象的connect方法(TCPWrap::Connect
), 我们到tcp_wrap.cc
中看一下:这里主要做了两件事:
这里简单看下
uv_tcp_connect
,uv_tcp_connect
是libuv中的方法,其最终调用的是uv__tcp_connect
方法,代码如下:这里主要做了两件事:
AfterConnect
中做了什么呢?也就是连接建立之后做了什么?我们一起看下AfterConnect的代码:这里主要执行了
req_wrap
的oncomplete
属性对用的函数,最终落在了net.js
中的afterConnect
方法,代码如下:其中
emit
了connect
和ready
事件。'data' 事件
当接收到数据的时候会触发'data'事件。
当Stream中有数据时,调用链如下:
StreamBase::ReadStartJS
->LibuvStreamWrap::ReadStart
->LibuvStreamWrap::OnUvRead
->StreamResource::EmitRead
->EmitToJSStreamListener::OnStreamRead
->CallJSOnreadMethod
->onRead
(net.js)其中
StreamBase::ReadStartJS
在TCPWrap::Initialize
方法中通过LibuvStreamWrap::AddMethods(env, t, StreamBase::kFlagHasWritev);
将其放在了JS对象的readStart
属性上。我们下面直接来看
net.js
中的onRead
方法,其代码如下:这里的
self.push(buffer)
,实际调用的是Readable.prototype.push
,这是因为net.js中的TCP类继承自stream.Duplex
,而stream.Duplex
又继承自Readable
。Readable.prototype.push
代码如下:这里主要调用了
readableAddChunk
方法,代码如下:这里
readableAddChunk
调用了addChunk
方法,addChunk
中调用stream.emit('data', chunk);
触发了'data'事件。总结
本文主要带着大家简要过了一遍
net
相关的实现,大家也许会发现,有了前面章节的铺垫,再看这些具体模块实现时就会轻松很多。