Node系列 · Node基础:net 模块
net是 Node 最底层的网络通信模块,提供基于 TCP / Unix 套接字的能力。HTTP、WebSocket、数据库驱动等模块几乎都建立在它之上。理解net就理解了 Node 网络编程的核心模型------socket 即文件、流即 socket。
一、net 模块定位
net 在 Node 模块体系里的位置如下图------所有上层网络通信都建立在它之上:
#mermaid-svg-ac7vYqSRbNp3Rjdx{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ac7vYqSRbNp3Rjdx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ac7vYqSRbNp3Rjdx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ac7vYqSRbNp3Rjdx .error-icon{fill:#552222;}#mermaid-svg-ac7vYqSRbNp3Rjdx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ac7vYqSRbNp3Rjdx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ac7vYqSRbNp3Rjdx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ac7vYqSRbNp3Rjdx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ac7vYqSRbNp3Rjdx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ac7vYqSRbNp3Rjdx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ac7vYqSRbNp3Rjdx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ac7vYqSRbNp3Rjdx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ac7vYqSRbNp3Rjdx .marker.cross{stroke:#333333;}#mermaid-svg-ac7vYqSRbNp3Rjdx svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ac7vYqSRbNp3Rjdx p{margin:0;}#mermaid-svg-ac7vYqSRbNp3Rjdx .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ac7vYqSRbNp3Rjdx .cluster-label text{fill:#333;}#mermaid-svg-ac7vYqSRbNp3Rjdx .cluster-label span{color:#333;}#mermaid-svg-ac7vYqSRbNp3Rjdx .cluster-label span p{background-color:transparent;}#mermaid-svg-ac7vYqSRbNp3Rjdx .label text,#mermaid-svg-ac7vYqSRbNp3Rjdx span{fill:#333;color:#333;}#mermaid-svg-ac7vYqSRbNp3Rjdx .node rect,#mermaid-svg-ac7vYqSRbNp3Rjdx .node circle,#mermaid-svg-ac7vYqSRbNp3Rjdx .node ellipse,#mermaid-svg-ac7vYqSRbNp3Rjdx .node polygon,#mermaid-svg-ac7vYqSRbNp3Rjdx .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ac7vYqSRbNp3Rjdx .rough-node .label text,#mermaid-svg-ac7vYqSRbNp3Rjdx .node .label text,#mermaid-svg-ac7vYqSRbNp3Rjdx .image-shape .label,#mermaid-svg-ac7vYqSRbNp3Rjdx .icon-shape .label{text-anchor:middle;}#mermaid-svg-ac7vYqSRbNp3Rjdx .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ac7vYqSRbNp3Rjdx .rough-node .label,#mermaid-svg-ac7vYqSRbNp3Rjdx .node .label,#mermaid-svg-ac7vYqSRbNp3Rjdx .image-shape .label,#mermaid-svg-ac7vYqSRbNp3Rjdx .icon-shape .label{text-align:center;}#mermaid-svg-ac7vYqSRbNp3Rjdx .node.clickable{cursor:pointer;}#mermaid-svg-ac7vYqSRbNp3Rjdx .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ac7vYqSRbNp3Rjdx .arrowheadPath{fill:#333333;}#mermaid-svg-ac7vYqSRbNp3Rjdx .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ac7vYqSRbNp3Rjdx .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ac7vYqSRbNp3Rjdx .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ac7vYqSRbNp3Rjdx .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ac7vYqSRbNp3Rjdx .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ac7vYqSRbNp3Rjdx .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ac7vYqSRbNp3Rjdx .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ac7vYqSRbNp3Rjdx .cluster text{fill:#333;}#mermaid-svg-ac7vYqSRbNp3Rjdx .cluster span{color:#333;}#mermaid-svg-ac7vYqSRbNp3Rjdx div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ac7vYqSRbNp3Rjdx .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ac7vYqSRbNp3Rjdx rect.text{fill:none;stroke-width:0;}#mermaid-svg-ac7vYqSRbNp3Rjdx .icon-shape,#mermaid-svg-ac7vYqSRbNp3Rjdx .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ac7vYqSRbNp3Rjdx .icon-shape p,#mermaid-svg-ac7vYqSRbNp3Rjdx .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ac7vYqSRbNp3Rjdx .icon-shape .label rect,#mermaid-svg-ac7vYqSRbNp3Rjdx .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ac7vYqSRbNp3Rjdx .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ac7vYqSRbNp3Rjdx .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ac7vYqSRbNp3Rjdx :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 基础层
应用层
基于
http
websocket
数据库驱动
自定义 TCP 服务
net 模块
TCP / IPC
tls 模块
TLS/SSL
操作系统 / 内核 / 网卡
| 通信类型 | 适用 | Node 模块 |
|---|---|---|
| TCP/IP 网络通信 | 跨主机、跨进程 | net |
| 进程间通信(IPC) | 同一台机器多进程 | net.connect({ path }) 用 Unix 域套接字 |
| HTTP 协议 | Web 服务 | http(基于 net) |
| TLS 加密 | HTTPS | tls(基于 net) |
二、Socket 概念
socket 是 TCP 通信的端点------一个虚拟的文件,可通过读写收发数据。
| 维度 | 说明 |
|---|---|
| 本质 | 操作系统分配的资源标识(文件描述符) |
| Node 表现 | net.Socket 实例,继承 Duplex 流(既可读也可写) |
| 通信方式 | 一端 write() 数据 → 另一端 data 事件触发 |
| 生命周期 | connect → data × N → end / error → close |
三、创建 TCP 客户端
3.1 最小客户端
javascript:net-client.js
const net = require('node:net');
const client = net.createConnection({ host: '127.0.0.1', port: 3000 }, () => {
console.log('已连接到服务端');
client.write('hello from client\n');
});
client.on('data', (data) => {
console.log('服务端说:', data.toString().trim());
client.end();
});
client.on('end', () => {
console.log('服务端关闭连接');
});
client.on('error', (err) => {
console.error('连接出错:', err.message);
});
3.2 返回值
net.createConnection 返回 net.Socket 实例(同时也是 Duplex 流)。所以:
- 可以
client.write(data)发数据 - 可以监听
data/end/error/close事件 - 可以
client.pipe(...)串到别处
四、创建 TCP 服务端
4.1 最简服务端
javascript:net-server.js
const net = require('node:net');
const server = net.createServer((socket) => {
console.log(`新客户端连接: ${socket.remoteAddress}:${socket.remotePort}`);
socket.write('欢迎连接\n');
socket.on('data', (data) => {
console.log(`收到: ${data.toString().trim()}`);
socket.write(`echo: ${data}`);
});
socket.on('end', () => {
console.log('客户端断开');
});
socket.on('error', (err) => {
console.error('socket 错误:', err.message);
});
});
server.listen(3000, () => {
console.log('服务端监听 3000 端口');
});
4.2 关键事件
| 事件 | 触发时机 | 用途 |
|---|---|---|
listening |
server.listen() 成功后 |
服务可用通知 |
connection |
新客户端连接(回调中第一个参数) | 给 socket 注册处理 |
close |
服务关闭且所有连接断开 | 资源清理 |
error |
出错(端口占用等) | 错误处理 |
::: warning
端口占用是 EADDRINUSE 错误 。换端口或先 server.close() 再重启。
:::
4.3 显式监听 vs 回调式创建
javascript:net-server-explicit.js
// 方式 1:callback 内处理(前面用的)
net.createServer((socket) => { /* ... */ }).listen(3000);
// 方式 2:先建 server,再 listen
const server = net.createServer();
server.on('connection', (socket) => { /* ... */ });
server.listen(3000, () => console.log('listening'));
两种等价;项目里选一种保持风格统一。
4.4 端口监听选项
javascript:net-listen-options.js
server.listen({
port: 3000,
host: '0.0.0.0', // 监听所有网卡(默认);'127.0.0.1' 只本机
backlog: 511, // TCP 等待连接队列长度(默认 511)
exclusive: false, // Node 14+:独占端口(cluster 场景)
}, () => {
const addr = server.address();
console.log(`监听 ${addr.address}:${addr.port}`);
});
五、回显服务端:完整示例
把客户端发来的数据原样返回:
text
echo-server/
├── server.js
└── client.js
javascript:echo-server.js
const net = require('node:net');
const server = net.createServer((socket) => {
socket.setEncoding('utf-8');
socket.write('欢迎连接 echo server,输入任意内容回显\n');
socket.on('data', (chunk) => {
const text = chunk.trim();
if (text === 'bye') {
socket.write('再见\n');
socket.end();
return;
}
socket.write(`echo: ${text}\n`);
});
});
server.listen(3000);
javascript:echo-client.js
const net = require('node:net');
const readline = require('node:readline');
const client = net.createConnection({ port: 3000 }, () => {
console.log('已连接,输入内容回显(输入 bye 退出)');
});
const rl = readline.createInterface({ input: process.stdin });
rl.on('line', (line) => {
client.write(`${line}\n`);
});
client.setEncoding('utf-8');
client.on('data', (chunk) => console.log(chunk.trim()));
client.on('end', () => {
console.log('连接关闭');
rl.close();
process.exit(0);
});
bash
$ node echo-server.js
$ node echo-client.js
已连接,输入内容回显(输入 bye 退出)
hello
echo: hello
bye
再见
连接关闭
六、socket 关键属性与方法
6.1 属性
| 属性 | 类型 | 说明 |
|---|---|---|
socket.remoteAddress |
string | 客户端 IP |
socket.remotePort |
number | 客户端端口 |
socket.localAddress |
string | 服务端本地 IP |
socket.localPort |
number | 服务端本地端口 |
socket.bytesRead |
number | 累计接收字节数 |
socket.bytesWritten |
number | 累计发送字节数 |
socket.readyState |
string | 'opening' / 'open' / 'readOnly' / 'writeOnly' / 'closed' |
6.2 方法
| 方法 | 作用 |
|---|---|
socket.write(data, [encoding], [cb]) |
发数据,返回 boolean(背压信号) |
socket.end([data], [encoding]) |
半关闭(FIN),后续只能接收 |
socket.destroy() |
强制关闭(不发 FIN) |
socket.setEncoding(encoding) |
设置接收数据编码 |
socket.setTimeout(ms, cb) |
空闲超时回调(无数据收发 ms 毫秒后触发) |
socket.setNoDelay(noDelay) |
true 关闭 Nagle 算法(适合实时通信) |
socket.setKeepAlive(enable, initialDelay) |
启用 TCP keep-alive |
6.3 空闲超时实战
javascript:net-timeout.js
const server = net.createServer((socket) => {
socket.setTimeout(30_000); // 30 秒无数据则超时
socket.on('timeout', () => {
console.log('客户端空闲超时,断开');
socket.end('idle timeout\n');
});
});
七、进程间通信(IPC)
同一台机器上的两个 Node 进程,可以用 Unix 域套接字通信:
text:ipc-demo/
ipc-parent.js
ipc-child.js
javascript:ipc-parent.js
const net = require('node:net');
const { fork } = require('node:child_process');
// 父进程:起服务
const server = net.createServer((socket) => {
socket.on('data', (data) => {
console.log('收到子进程:', data.toString().trim());
socket.write('ack from parent\n');
});
});
server.listen('/tmp/my-ipc.sock', () => {
fork('./ipc-child.js');
});
javascript:ipc-child.js
const net = require('node:net');
const client = net.createConnection('/tmp/my-ipc.sock', () => {
client.write('hello parent\n');
});
client.on('data', (data) => {
console.log('父进程响应:', data.toString().trim());
client.end();
});
::: tip
实际项目里用 child_process.fork 自带的 IPC 通道(process.send / process.on('message'))就够了,不需要自己写 Unix 域套接字。上面的例子只用于"必须用 raw net" 的场景。
:::
八、socket 与 EventEmitter 的关系
net.Socket 继承自 stream.Duplex,stream.Duplex 又继承自 stream.Stream,最终继承自 events.EventEmitter。这就是为什么 socket 能用 on('data', ...) 这种事件 API------本质上都是 EventEmitter。
后续学 http.ServerRequest / http.ServerResponse 时会发现它们也是同样的继承链。
九、net 与 http 的关系
http.Server 内部就是 net.Server,每条 HTTP 连接对应一个 socket:
#mermaid-svg-5b77fY12qjmP9Z7x{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-5b77fY12qjmP9Z7x .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5b77fY12qjmP9Z7x .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5b77fY12qjmP9Z7x .error-icon{fill:#552222;}#mermaid-svg-5b77fY12qjmP9Z7x .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5b77fY12qjmP9Z7x .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5b77fY12qjmP9Z7x .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5b77fY12qjmP9Z7x .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5b77fY12qjmP9Z7x .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5b77fY12qjmP9Z7x .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5b77fY12qjmP9Z7x .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5b77fY12qjmP9Z7x .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5b77fY12qjmP9Z7x .marker.cross{stroke:#333333;}#mermaid-svg-5b77fY12qjmP9Z7x svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5b77fY12qjmP9Z7x p{margin:0;}#mermaid-svg-5b77fY12qjmP9Z7x .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-5b77fY12qjmP9Z7x .cluster-label text{fill:#333;}#mermaid-svg-5b77fY12qjmP9Z7x .cluster-label span{color:#333;}#mermaid-svg-5b77fY12qjmP9Z7x .cluster-label span p{background-color:transparent;}#mermaid-svg-5b77fY12qjmP9Z7x .label text,#mermaid-svg-5b77fY12qjmP9Z7x span{fill:#333;color:#333;}#mermaid-svg-5b77fY12qjmP9Z7x .node rect,#mermaid-svg-5b77fY12qjmP9Z7x .node circle,#mermaid-svg-5b77fY12qjmP9Z7x .node ellipse,#mermaid-svg-5b77fY12qjmP9Z7x .node polygon,#mermaid-svg-5b77fY12qjmP9Z7x .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-5b77fY12qjmP9Z7x .rough-node .label text,#mermaid-svg-5b77fY12qjmP9Z7x .node .label text,#mermaid-svg-5b77fY12qjmP9Z7x .image-shape .label,#mermaid-svg-5b77fY12qjmP9Z7x .icon-shape .label{text-anchor:middle;}#mermaid-svg-5b77fY12qjmP9Z7x .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-5b77fY12qjmP9Z7x .rough-node .label,#mermaid-svg-5b77fY12qjmP9Z7x .node .label,#mermaid-svg-5b77fY12qjmP9Z7x .image-shape .label,#mermaid-svg-5b77fY12qjmP9Z7x .icon-shape .label{text-align:center;}#mermaid-svg-5b77fY12qjmP9Z7x .node.clickable{cursor:pointer;}#mermaid-svg-5b77fY12qjmP9Z7x .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-5b77fY12qjmP9Z7x .arrowheadPath{fill:#333333;}#mermaid-svg-5b77fY12qjmP9Z7x .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-5b77fY12qjmP9Z7x .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-5b77fY12qjmP9Z7x .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5b77fY12qjmP9Z7x .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-5b77fY12qjmP9Z7x .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5b77fY12qjmP9Z7x .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-5b77fY12qjmP9Z7x .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-5b77fY12qjmP9Z7x .cluster text{fill:#333;}#mermaid-svg-5b77fY12qjmP9Z7x .cluster span{color:#333;}#mermaid-svg-5b77fY12qjmP9Z7x div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-5b77fY12qjmP9Z7x .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-5b77fY12qjmP9Z7x rect.text{fill:none;stroke-width:0;}#mermaid-svg-5b77fY12qjmP9Z7x .icon-shape,#mermaid-svg-5b77fY12qjmP9Z7x .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5b77fY12qjmP9Z7x .icon-shape p,#mermaid-svg-5b77fY12qjmP9Z7x .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-5b77fY12qjmP9Z7x .icon-shape .label rect,#mermaid-svg-5b77fY12qjmP9Z7x .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5b77fY12qjmP9Z7x .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-5b77fY12qjmP9Z7x .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-5b77fY12qjmP9Z7x :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} TCP 三次握手
on data
write
TCP 挥手
浏览器
socket
http.IncomingMessage
业务 handler
http.ServerResponse
理解 net 后,HTTP 的 req / res 就只是"加了 HTTP 协议解析的 socket"------一切 socket 方法(write / setTimeout / destroy)在 req / res 上也都能用。
十、最佳实践
| 场景 | 推荐 |
|---|---|
| TCP 服务端 | net.createServer(cb).listen(port) |
| TCP 客户端 | net.createConnection({ host, port }) |
| 数据结束判断 | 协议层自定义结束符(如 \n)或固定长度 |
| 大数据传输 | 用流(socket.pipe(writable)) |
| 错误处理 | 必须监听 'error'------不监听未捕获异常直接 crash |
| 空闲连接 | socket.setTimeout 主动断开 |
| 高并发 | 用 cluster 模块或反向代理,不要单进程硬扛 |
十一、小结
net是 Node 网络通信的最底层;http/tls都基于它- 服务端
net.createServer+listen,客户端net.createConnection net.Socket是 Duplex 流,既能写也能读,事件 API 与EventEmitter一致- 同一进程内的多进程通信可用 IPC(
path选项的 Unix 域套接字) - TCP 通信要解决四个问题:连接管理、协议解析、背压、空闲超时------
net给出了基础设施,业务层要负责协议层