{T}

Net 模块

概述

net 模块是 Node.js 网络层的基石,提供异步的 TCP 和 IPC(进程间通信)能力。httphttpsws 等上层模块都构建在 net 之上。理解 net 模块,就是理解 Node.js 网络编程的最底层机制——TCP 连接管理、Socket 生命周期、IPC 通道、Cluster 架构等。

图表渲染中…

TCP 连接生命周期

三次握手与四次挥手

图表渲染中…

Node.js 中的映射

图表渲染中…

net.Server — TCP 服务器

创建服务器

javascript
const net = require('net');

const server = net.createServer((socket) => {
  console.log(`新连接: ${socket.remoteAddress}:${socket.remotePort}`);

  socket.on('data', (data) => {
    console.log(`收到数据: ${data}`);
    socket.write(`Echo: ${data}`);
  });

  socket.on('end', () => {
    console.log('客户端断开连接');
  });

  socket.on('error', (err) => {
    console.error('Socket 错误:', err.message);
  });
});

server.listen(3000, () => {
  console.log(`服务器监听 ${JSON.stringify(server.address())}`);
});

Server 事件

事件触发时机回调参数
listening服务器开始监听
connection新连接建立socket
close服务器关闭
error服务器错误Error

Server 配置

javascript
const server = net.createServer({
  allowHalfOpen: false,    // 半关闭模式(默认 false)
  pauseOnConnect: false,   // 连接时暂停 socket(默认 false)
  keepAlive: true,         // 启用 TCP Keep-Alive
  keepAliveInitialDelay: 5000,  // 首次探测延迟
});

server.maxConnections = 1000;  // 最大连接数
server.getConnections((err, count) => {
  console.log(`当前连接数: ${count}`);
});

net.Socket — 双工连接

net.SocketDuplex Stream 的子类,同时可读可写:

图表渲染中…

Socket 事件

事件触发时机回调参数
data收到数据Buffer
end对端发送 FIN
close连接完全关闭hadError
error发生错误Error
timeout连接超时
connect客户端连接成功
drain写缓冲区清空

Nagle 算法与 setNoDelay

TCP 默认启用 Nagle 算法——将小数据包合并发送以减少网络开销。但这会增加延迟:

图表渲染中…
javascript
// 禁用 Nagle 算法 — 适用于需要低延迟的场景
socket.setNoDelay(true);

// 实时游戏、SSH、Redis 协议等需要禁用 Nagle
// HTTP、文件传输等可以保持默认

TCP Keep-Alive

javascript
// 启用 TCP Keep-Alive
socket.setKeepAlive(true, 5000);  // 每 5 秒探测一次

// Keep-Alive 探测机制
// 1. 空闲 5 秒后发送探测包
// 2. 对端无响应 → 重试
// 3. 多次无响应 → 关闭连接
图表渲染中…

TCP 客户端

javascript
const net = require('net');

const client = net.createConnection({
  host: 'example.com',
  port: 80,
}, () => {
  console.log('连接到服务器');
  client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n');
});

client.on('data', (data) => {
  console.log(data.toString());
  client.end();
});

client.on('end', () => {
  console.log('断开连接');
});

client.on('error', (err) => {
  console.error('连接错误:', err.message);
});

IPC — 进程间通信

net 模块除了 TCP,还支持 IPC(Inter-Process Communication),通过 Unix Domain Socket(Linux/macOS)或 Named Pipe(Windows)实现同机进程间的高效通信:

图表渲染中…

IPC 服务器

javascript
const server = net.createServer((socket) => {
  socket.on('data', (data) => {
    console.log('IPC 收到:', data.toString());
  });
});

// Linux/macOS: Unix Domain Socket
server.listen('/tmp/node-ipc.sock');

// Windows: Named Pipe
// server.listen('\\\\.\\pipe\\node-ipc');

IPC 客户端

javascript
const client = net.createConnection('/tmp/node-ipc.sock', () => {
  client.write('Hello from client');
});

IPC 与 TCP 的性能对比

维度TCPIPC (Unix Socket)
传输路径网络协议栈内核内存拷贝
延迟较高极低
吞吐量受网络限制接近内存速度
安全性需防火墙文件系统权限
跨机器

Cluster — 多进程架构

cluster 模块利用 IPC 通道实现多进程架构——Master 进程监听端口,通过轮询将连接分发给 Worker 进程:

图表渲染中…

Cluster 工作原理

javascript
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if (cluster.isPrimary) {
  console.log(`Master ${process.pid} 运行中`);

  // Fork Worker 进程
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on('exit', (worker, code, signal) => {
    console.log(`Worker ${worker.process.pid} 退出,重启中...`);
    cluster.fork();  // 自动重启
  });
} else {
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end(`Worker ${process.pid} 处理了此请求\n`);
  }).listen(3000);

  console.log(`Worker ${process.pid} 启动`);
}

两种调度策略

策略说明平台默认
Round-RobinMaster 轮询分发连接给 WorkerLinux/macOS
Shared Port所有 Worker 共享同一个端口,内核决定分配Windows
图表渲染中…
javascript
// 显式设置调度策略
cluster.schedulingPolicy = cluster.SCHED_RR;   // Round-Robin
cluster.schedulingPolicy = cluster.SCHED_NONE;  // Shared Port

Worker 间通信

javascript
// Master → Worker
worker.send({ type: 'config', data: { port: 3000 } });

// Worker → Master
process.send({ type: 'ready', pid: process.pid });

// Master 监听 Worker 消息
worker.on('message', (msg) => {
  if (msg.type === 'ready') {
    console.log(`Worker ${msg.pid} 已就绪`);
  }
});

连接分发流程

图表渲染中…

关键点:Round-Robin 模式下,连接的接受由 Master 完成,但建立后的数据传输直接在 Worker 和客户端之间进行,不经过 Master 转发——这是通过传递文件描述符(fd)实现的。

常见陷阱

1. TCP 粘包问题

TCP 是流式协议,不保证消息边界:

javascript
// 发送方
socket.write('Hello');
socket.write('World');

// 接收方可能收到
// 情况1: 'Hello' 和 'World' 分两次 data 事件
// 情况2: 'HelloWorld' 一次 data 事件(粘包)

解决方案:在应用层定义消息边界

javascript
// 方案1:固定长度
// 每条消息固定 64 字节

// 方案2:分隔符
socket.write('Hello\n');
socket.write('World\n');

// 方案3:长度前缀(推荐)
function sendMessage(socket, data) {
  const payload = Buffer.from(data);
  const header = Buffer.alloc(4);
  header.writeUInt32BE(payload.length, 0);
  socket.write(Buffer.concat([header, payload]));
}

function receiveMessage(socket) {
  let buffer = Buffer.alloc(0);
  socket.on('data', (chunk) => {
    buffer = Buffer.concat([buffer, chunk]);
    while (buffer.length >= 4) {
      const len = buffer.readUInt32BE(0);
      if (buffer.length < 4 + len) break;
      const message = buffer.subarray(4, 4 + len).toString();
      console.log('收到消息:', message);
      buffer = buffer.subarray(4 + len);
    }
  });
}

2. socket.write 返回 false 后未等 drain

javascript
// ❌ 持续写入可能耗尽内存
while (dataToWrite) {
  socket.write(dataToWrite);
}

// ✅ 处理背压
function writeData(socket, data) {
  const ok = socket.write(data);
  if (!ok) {
    socket.once('drain', () => {
      writeData(socket, nextChunk());
    });
  }
}

3. 未处理 socket 错误导致进程崩溃

javascript
// ❌ 未监听 error 事件
const socket = net.createConnection(3000);
// 如果连接失败 → Uncaught Error → 进程崩溃

// ✅ 始终监听 error
socket.on('error', (err) => {
  console.error('Socket error:', err.message);
});

4. TIME_WAIT 状态导致端口占用

javascript
// 服务端重启时端口可能还在 TIME_WAIT
server.listen(3000);  // EADDRINUSE

// 解决:允许地址复用
server.listen(3000, () => {
  // ...
});
// 或设置
server.on('error', (err) => {
  if (err.code === 'EADDRINUSE') {
    setTimeout(() => server.listen(3000), 1000);
  }
});