简介
Node.js 的 net 模块提供了用于实现 TCP 服务器和客户端的异步网络 API。它是 Node.js 网络功能的核心,为上层模块如 HTTP、HTTPS 等提供了基础支持。本教程将全面介绍 net 模块的使用方法和最佳实践。
引入 net 模块
1 | const net = require('net'); |
核心概念
TCP (传输控制协议)
TCP 是一种面向连接的、可靠的、基于字节流的传输层通信协议。net 模块主要处理 TCP 通信。
Socket
Socket 是网络通信的端点,在 Node.js 中表示为 net.Socket 类的实例。它可以是服务器与客户端之间建立的连接,也可以是客户端主动创建的连接。
服务器
服务器使用 net.Server 类创建,负责监听连接并处理客户端请求。
TCP 服务器创建
基本服务器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 1 | const net = require('net'); // 创建服务器 const server = net.createServer((socket) => { console.log('客户端已连接'); // 接收数据 socket.on('data', (data) => { console.log(`接收到数据: ${data}`); // 发送响应 socket.write('服务器已收到你的消息'); }); // 连接关闭 socket.on('end', () => { console.log('客户端已断开连接'); }); // 处理错误 socket.on('error', (err) => { console.error('连接错误:', err); }); }); // 监听端口 server.listen(3000, () => { console.log('服务器启动成功,监听端口 3000'); }); |
服务器配置选项
创建服务器时可以传递配置选项:
1 2 3 4 | const server = net.createServer({ allowHalfOpen: false, // 当另一端发送 FIN 包时自动发送 FIN (默认) pauseOnConnect: false // 是否在连接时暂停套接字 (默认) }); |
服务器事件
net.Server 类继承自 EventEmitter,支持以下主要事件:
- listening: 服务器开始监听连接时触发
- connection: 新客户端连接建立时触发
- error: 发生错误时触发
- close: 服务器关闭时触发
1 2 3 4 5 6 7 8 9 10 11 12 | server.on('listening', () => { console.log('服务器开始监听连接'); }); server.on('connection', (socket) => { console.log('新客户端连接'); }); server.on('error', (err) => { console.error('服务器错误:', err); }); server.on('close', () => { console.log('服务器已关闭'); }); |
TCP 客户端创建
基本客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 1 | const net = require('net'); // 创建连接 const client = net.createConnection({ host: 'localhost', port: 3000 }, () => { console.log('已连接到服务器'); // 发送数据 client.write('你好,服务器'); }); // 接收数据 client.on('data', (data) => { console.log(`接收到服务器响应: ${data}`); // 关闭连接 client.end(); }); // 连接结束 client.on('end', () => { console.log('已断开与服务器的连接'); }); // 错误处理 client.on('error', (err) => { console.error('连接错误:', err); }); |
客户端配置选项
创建客户端连接时可以传递多种配置选项:
1 2 3 4 5 6 7 | const client = net.createConnection({ host: 'localhost', // 主机名 port: 3000, // 端口号 localAddress: '192.168.1.100', // 本地接口 family: 4, // IP 版本 (4 或 6) timeout: 5000 // 连接超时(毫秒) }); |
Socket 对象
net.Socket 是 TCP 连接的抽象,具有流(Duplex Stream)的特性,既可读又可写。
创建 Socket
除了服务器自动创建外,也可以手动创建:
1 2 3 4 | const socket = new net.Socket(); socket.connect(3000, 'localhost', () => { console.log('连接成功'); }); |
Socket 属性
- socket.remoteAddress: 远程 IP 地址
- socket.remotePort: 远程端口
- socket.localAddress: 本地 IP 地址
- socket.localPort: 本地端口
- socket.bytesRead: 接收的字节数
- socket.bytesWritten: 发送的字节数
1 2 3 4 | socket.on('connect', () => { console.log(`连接到 ${socket.remoteAddress}:${socket.remotePort}`); console.log(`本地端口: ${socket.localPort}`); }); |
Socket 方法
- socket.write(data[, encoding][, callback]): 发送数据
- socket.end([data][, encoding][, callback]): 结束连接
- socket.destroy([error]): 强制关闭连接
- socket.pause(): 暂停数据读取
- socket.resume(): 恢复数据读取
- socket.setKeepAlive([enable][, initialDelay]): 设置 keepalive
- socket.setNoDelay([noDelay]): 禁用 Nagle 算法
事件处理
服务器事件
1 2 3 4 5 6 7 8 9 10 | server.on('listening', () => { const address = server.address(); console.log(`服务器监听 ${address.address}:${address.port}`); }); server.on('error', (err) => { if (err.code === 'EADDRINUSE') { console.error('端口已被占用'); } }); |
Socket 事件
- connect: 成功建立连接时触发
- data: 接收到数据时触发
- end: 对方结束发送数据时触发
- timeout: 连接超时时触发
- error: 发生错误时触发
- close: 连接完全关闭时触发
1 2 3 4 5 6 7 8 9 10 1 | socket.on('data', (data) => { console.log(`接收到数据: ${data.toString()}`); }); socket.on('timeout', () => { console.log('连接超时'); socket.end(); }); socket.on('close', (hadError) => { console.log(`连接关闭${hadError ? ',发生错误' : ''}`); }); |
数据传输
发送数据
1 2 3 4 5 6 7 8 9 10 | // 发送字符串 socket.write('Hello', 'utf8'); // 发送 Buffer const buffer = Buffer.from([0x68, 0x65, 0x6c, 0x6c, 0x6f]); socket.write(buffer); // 使用回调确认数据已被发送 socket.write('World', () => { console.log('数据已发送'); }); |
接收数据
1 2 3 4 5 6 7 8 9 1 | let chunks = []; socket.on('data', (chunk) => { chunks.push(chunk); }); socket.on('end', () => { const data = Buffer.concat(chunks).toString(); console.log(`完整数据: ${data}`); }); |
处理二进制数据
1 2 3 4 5 6 | socket.on('data', (chunk) => { // 假设前两个字节表示消息长度 const messageLength = chunk.readUInt16BE(0); const message = chunk.slice(2, 2 + messageLength); console.log(`消息内容: ${message.toString()}`); }); |
高级特性
IPC (进程间通信)
除了 TCP 通信,net 模块也支持通过 Unix 域套接字或命名管道进行进程间通信:
1 2 3 4 5 | // 服务器 const server = net.createServer().listen('/tmp/echo.sock'); // 客户端 const client = net.createConnection({ path: '/tmp/echo.sock' }); |
多连接管理
实际应用中,服务器通常需要管理多个连接:
1 2 3 4 5 6 7 8 9 10 11 12 13 1 | const connections = new Map(); server.on('connection', (socket) => { const id = `${socket.remoteAddress}:${socket.remotePort}`; connections.set(id, socket); socket.on('close', () => { connections.delete(id); console.log(`客户端 ${id} 已断开,当前连接数: ${connections.size}`); }); }); // 向所有客户端广播消息 function broadcast(message) { for (const socket of connections.values()) { socket.write(message); } } |
重连机制
客户端断线重连示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 1 | function createClient() { const client = net.createConnection({ port: 3000 }); client.on('error', (err) => { console.error('连接错误:', err); }); client.on('close', () => { console.log('连接关闭,尝试重连...'); setTimeout(() => { createClient(); }, 3000); // 3秒后重连 }); return client; } const client = createClient(); |
实际应用案例
简单聊天服务器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 2 | const net = require('net'); const clients = []; const server = net.createServer((socket) => { // 为新连接分配昵称 socket.name = `用户${clients.length + 1}`; // 广播新用户连接消息 const message = `${socket.name} 已加入聊天室`; broadcast(message, socket); // 添加到客户端列表 clients.push(socket); // 欢迎消息 socket.write(`欢迎来到聊天室,${socket.name}!\n`); // 接收消息 socket.on('data', (data) => { broadcast(`${socket.name}: ${data}`, socket); }); // 断开连接 socket.on('end', () => { clients.splice(clients.indexOf(socket), 1); broadcast(`${socket.name} 已离开聊天室`, socket); }); // 处理错误 socket.on('error', (err) => { console.error(`${socket.name} 发生错误:`, err); }); }); // 广播消息给所有客户端 function broadcast(message, sender) { clients.forEach((client) => { // 不发送给消息发送者 if (client !== sender) { client.write(message); } }); console.log(message); } server.listen(3000, () => { console.log('聊天服务器已启动,监听端口 3000'); }); |
简单的 HTTP 服务器
使用 net 模块实现基础 HTTP 服务器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 1 | const net = require('net'); const server = net.createServer((socket) => { socket.on('data', (data) => { const request = data.toString(); console.log('收到请求:', request); // 简单的 HTTP 响应 const response = [ 'HTTP/1.1 200 OK', 'Content-Type: text/html', 'Connection: close', '', '<html><body><h1>Hello from Node.js net module</h1></body></html>' ].join('\r\n'); socket.write(response); socket.end(); }); socket.on('error', (err) => { console.error('Socket 错误:', err); }); }); server.listen(8080, () => { console.log('HTTP 服务器运行在 http://localhost:8080/'); }); |
性能优化
使用 Buffer 池
对于高性能应用,可以使用 Buffer 池避免频繁创建新 Buffer:
1 2 3 4 5 6 7 8 9 10 11 12 13 1 | const bufferPool = Buffer.allocUnsafe(1024 * 100); // 100KB 池 let offset = 0; function allocateBuffer(size) { if (offset + size > bufferPool.length) { offset = 0; // 重置偏移 } const buffer = bufferPool.slice(offset, offset + size); offset += size; return buffer; } // 使用预分配的 buffer 发送数据 const dataToSend = "Hello"; const buffer = allocateBuffer(dataToSend.length); buffer.write(dataToSend); socket.write(buffer); |
避免小包发送
合并小数据包可以提高网络效率:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 1 | const queue = []; let isFlushing = false; function queueData(socket, data) { queue.push(data); if (!isFlushing) { isFlushing = true; process.nextTick(flushQueue, socket); } } function flushQueue(socket) { if (queue.length > 0) { const data = Buffer.concat(queue); queue.length = 0; socket.write(data); } isFlushing = false; } |
调整 Socket 参数
针对不同场景优化 Socket 设置:
1 2 3 4 5 6 7 8 9 10 1 | // 低延迟应用 (禁用 Nagle 算法) socket.setNoDelay(true); // 长连接应用 socket.setKeepAlive(true, 60000); // 60秒 // 设置超时 socket.setTimeout(30000); // 30秒 socket.on('timeout', () => { console.log('连接超时'); socket.end(); }); |
常见问题解答
Q: 如何处理 EADDRINUSE 错误?
A: 这个错误表示端口已被占用,可以通过以下方式处理:
1 2 3 4 5 6 7 | server.on('error', (err) => { if (err.code === 'EADDRINUSE') { console.log('端口已被占用,尝试其他端口...'); server.close(); server.listen(port + 1); } }); |
Q: 如何实现心跳机制?
A: 通过定时发送心跳包确保连接活跃:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // 服务端心跳检测 const clients = new Map(); server.on('connection', (socket) => { const id = `${socket.remoteAddress}:${socket.remotePort}`; clients.set(id, { socket, lastHeartbeat: Date.now() }); socket.on('data', (data) => { if (data.toString() === 'PING') { clients.get(id).lastHeartbeat = Date.now(); socket.write('PONG'); } }); }); // 每10秒检查一次客户端心跳 setInterval(() => { const now = Date.now(); for (const [id, client] of clients.entries()) { // 如果客户端30秒没有心跳,断开连接 if (now - client.lastHeartbeat > 30000) { console.log(`客户端 ${id} 心跳超时,断开连接`); client.socket.destroy(); clients.delete(id); } } }, 10000); // 客户端心跳 const client = net.createConnection({ port: 3000 }); setInterval(() => { client.write('PING'); }, 10000); |
Q: 如何处理大量数据传输?
A: 使用流控制和数据分块:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | const fs = require('fs'); // 发送大文件 function sendLargeFile(socket, filePath) { const fileStream = fs.createReadStream(filePath); fileStream.on('data', (chunk) => { // 检查缓冲区是否已满 const canContinue = socket.write(chunk); if (!canContinue) { // 如果缓冲区已满,暂停读取 fileStream.pause(); // 当缓冲区清空后,恢复读取 socket.once('drain', () => { fileStream.resume(); }); } }); fileStream.on('end', () => { console.log('文件发送完成'); }); } |