Node.js中的TCP模块深度解析
【免费下载链接】understand-nodejs 项目地址: https://gitcode.com/gh_mirrors/un/understand-nodejs
前言
TCP作为传输层协议,在网络编程中扮演着至关重要的角色。Node.js通过net模块为我们提供了TCP相关的API,使得开发者能够轻松构建TCP服务器和客户端。本文将深入分析Node.js中TCP模块的实现原理,帮助读者理解其底层工作机制。
TCP基础回顾
在深入Node.js实现之前,我们先回顾一下TCP编程的基本流程:
TCP服务器创建流程
- 创建socket:申请一个socket结构体
- bind绑定:为socket设置IP和端口
- listen监听:将socket设置为监听状态
- accept接受连接:等待客户端连接
const fd = socket();
bind(fd, ip, port);
listen(fd);
const acceptedFd = accept(fd);
handle(acceptedFd);
TCP客户端创建流程
- 创建socket:申请一个socket结构体
- connect连接:发起三次握手
- write写入数据:向服务器发送数据
const fd = socket();
const connectRet = connect(fd, ip, port);
write(fd, 'hello');
Node.js中的TCP实现
Node.js通过net模块封装了TCP功能,下面我们从客户端和服务器两个角度来分析其实现。
TCP客户端实现
1. 建立连接
Node.js通过net.connect()方法创建TCP客户端连接:
function connect(...args) {
var normalized = normalizeArgs(args);
var options = normalized[0];
var socket = new Socket(options);
if (options.timeout) {
socket.setTimeout(options.timeout);
}
return Socket.prototype.connect.call(socket, normalized);
}
这个过程主要分为三个步骤:
- 创建Socket对象:初始化客户端socket
- 设置超时:配置连接超时处理
- 发起连接:执行实际的连接操作
Socket对象初始化
function Socket(options) {
this.connecting = false; // 是否正在连接
this._hadError = false; // 是否发生错误
this._handle = null; // 底层handle
this[kTimeout] = null; // 超时定时器ID
// 继承双工流特性
stream.Duplex.call(this, options);
// 初始状态不可读写
this.readable = this.writable = false;
// 注册事件回调
this.on('finish', onSocketFinish);
this.on('_socketEnd', onSocketEnd);
// 是否允许半关闭
this.allowHalfOpen = options && options.allowHalfOpen || false;
}
超时设置
Socket.prototype.setTimeout = function(msecs, callback) {
clearTimeout(this[kTimeout]);
if (msecs === 0) {
if (callback) this.removeListener('timeout', callback);
} else {
this[kTimeout] = setUnrefTimeout(this._onTimeout.bind(this), msecs);
if (callback) this.once('timeout', callback);
}
return this;
};
Socket.prototype._onTimeout = function() {
this.emit('timeout');
};
注意:超时回调需要开发者自行处理,比如关闭连接:
socket.setTimeout(10000);
socket.on('timeout', () => {
socket.close();
});
连接过程
Socket.prototype.connect = function(...args) {
let normalized;
/* 参数处理 */
var options = normalized[0];
var cb = normalized[1];
// 创建底层TCP handle
this._handle = new TCP(TCPConstants.SOCKET);
this._handle.onread = onread;
if (cb !== null) {
this.once('connect', cb);
}
this.connecting = true;
this.writable = true;
this._unrefTimer();
// 可能需要DNS解析
lookupAndConnect(this, options);
return this;
};
底层通过C++的TCPWrap类实现:
void TCPWrap::New(const FunctionCallbackInfo<Value>& args) {
CHECK(args.IsConstructCall());
CHECK(args[0]->IsInt32());
Environment* env = Environment::GetCurrent(args);
int type_value = args[0].As<Int32>()->Value();
TCPWrap::SocketType type = static_cast<TCPWrap::SocketType>(type_value);
ProviderType provider;
switch (type) {
case SOCKET: provider = PROVIDER_TCPWRAP; break; // 客户端
case SERVER: provider = PROVIDER_TCPSERVERWRAP; break; // 服务器
default: UNREACHABLE();
}
new TCPWrap(env, args.This(), provider);
}
2. 读操作
当连接建立后,Node.js会自动开始监听可读事件:
Socket.prototype._read = function(n) {
if (this.connecting || !this._handle) {
this.once('connect', () => this._read(n));
} else if (!this._handle.reading) {
this._handle.reading = true;
var err = this._handle.readStart();
if (err)
this.destroy(errnoException(err, 'read'));
}
};
底层通过libuv的uv_read_start注册读事件:
int LibuvStreamWrap::ReadStart() {
return uv_read_start(stream(),
[](uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) {
static_cast<LibuvStreamWrap*>(handle->data)->OnUvAlloc(suggested_size, buf);
},
[](uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
static_cast<LibuvStreamWrap*>(stream->data)->OnUvRead(nread, buf);
});
}
当数据到达时,会触发JS层的onread回调:
function onread(nread, buffer) {
var handle = this;
var self = handle.owner;
self._unrefTimer();
if (nread > 0) {
var ret = self.push(buffer);
if (handle.reading && !ret) {
handle.reading = false;
var err = handle.readStop();
if (err) self.destroy(errnoException(err, 'read'));
}
return;
}
if (nread === 0) return;
if (nread !== UV_EOF) return self.destroy(errnoException(nread, 'read'));
self.push(null);
if (self.readableLength === 0) {
self.readable = false;
maybeDestroy(self);
}
self.emit('_socketEnd');
}
3. 写操作
Node.js提供了单个写和批量写两种方式:
// 批量写
Socket.prototype._writev = function(chunks, cb) {
this._writeGeneric(true, chunks, '', cb);
};
// 单个写
Socket.prototype._write = function(data, encoding, cb) {
this._writeGeneric(false, data, encoding, cb);
};
核心写逻辑在_writeGeneric中:
Socket.prototype._writeGeneric = function(writev, data, encoding, cb) {
if (this.connecting) {
this._pendingData = data;
this._pendingEncoding = encoding;
this.once('connect', function connect() {
this._writeGeneric(writev, data, encoding, cb);
});
return;
}
this._pendingData = null;
this._pendingEncoding = '';
this._unrefTimer();
if (!this._handle) {
this.destroy(new errors.Error('ERR_SOCKET_CLOSED'), cb);
return false;
}
var req = new WriteWrap();
req.handle = this._handle;
req.oncomplete = afterWrite;
req.async = false;
var err;
if (writev) {
// 批量写处理逻辑
} else {
// 单个写处理逻辑
}
if (err) return this.destroy(errnoException(err, 'write', req.error), cb);
this._bytesDispatched += req.bytes;
// 其他处理...
};
总结
Node.js的TCP模块通过以下几个关键点实现了高效的网络通信:
- 事件驱动:基于libuv的事件循环机制,实现非阻塞IO
- 流式接口:继承自Stream模块,提供统一的读写接口
- 底层封装:通过C++层封装操作系统原生TCP API
- 连接管理:完善的连接状态管理和错误处理机制
理解这些底层实现原理,有助于我们更好地使用Node.js进行网络编程,并在遇到问题时能够快速定位和解决。
【免费下载链接】understand-nodejs 项目地址: https://gitcode.com/gh_mirrors/un/understand-nodejs
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



