Node.js net模塊的使用示例
簡介
Node.js 的 net 模塊提供了用于實現(xiàn) TCP 服務器和客戶端的異步網(wǎng)絡 API。它是 Node.js 網(wǎng)絡功能的核心,為上層模塊如 HTTP、HTTPS 等提供了基礎支持。本教程將全面介紹 net 模塊的使用方法和最佳實踐。
引入 net 模塊
const net = require('net');
核心概念
TCP (傳輸控制協(xié)議)
TCP 是一種面向連接的、可靠的、基于字節(jié)流的傳輸層通信協(xié)議。net 模塊主要處理 TCP 通信。
Socket
Socket 是網(wǎng)絡通信的端點,在 Node.js 中表示為 net.Socket 類的實例。它可以是服務器與客戶端之間建立的連接,也可以是客戶端主動創(chuàng)建的連接。
服務器
服務器使用 net.Server 類創(chuàng)建,負責監(jiān)聽連接并處理客戶端請求。
TCP 服務器創(chuàng)建
基本服務器
const net = require('net');
// 創(chuàng)建服務器
const server = net.createServer((socket) => {
console.log('客戶端已連接');
// 接收數(shù)據(jù)
socket.on('data', (data) => {
console.log(`接收到數(shù)據(jù): ${data}`);
// 發(fā)送響應
socket.write('服務器已收到你的消息');
});
// 連接關(guān)閉
socket.on('end', () => {
console.log('客戶端已斷開連接');
});
// 處理錯誤
socket.on('error', (err) => {
console.error('連接錯誤:', err);
});
});
// 監(jiān)聽端口
server.listen(3000, () => {
console.log('服務器啟動成功,監(jiān)聽端口 3000');
});
服務器配置選項
創(chuàng)建服務器時可以傳遞配置選項:
const server = net.createServer({
allowHalfOpen: false, // 當另一端發(fā)送 FIN 包時自動發(fā)送 FIN (默認)
pauseOnConnect: false // 是否在連接時暫停套接字 (默認)
});
服務器事件
net.Server 類繼承自 EventEmitter,支持以下主要事件:
listening: 服務器開始監(jiān)聽連接時觸發(fā)connection: 新客戶端連接建立時觸發(fā)error: 發(fā)生錯誤時觸發(fā)close: 服務器關(guān)閉時觸發(fā)
server.on('listening', () => {
console.log('服務器開始監(jiān)聽連接');
});
server.on('connection', (socket) => {
console.log('新客戶端連接');
});
server.on('error', (err) => {
console.error('服務器錯誤:', err);
});
server.on('close', () => {
console.log('服務器已關(guān)閉');
});
TCP 客戶端創(chuàng)建
基本客戶端
const net = require('net');
// 創(chuàng)建連接
const client = net.createConnection({
host: 'localhost',
port: 3000
}, () => {
console.log('已連接到服務器');
// 發(fā)送數(shù)據(jù)
client.write('你好,服務器');
});
// 接收數(shù)據(jù)
client.on('data', (data) => {
console.log(`接收到服務器響應: ${data}`);
// 關(guān)閉連接
client.end();
});
// 連接結(jié)束
client.on('end', () => {
console.log('已斷開與服務器的連接');
});
// 錯誤處理
client.on('error', (err) => {
console.error('連接錯誤:', err);
});
客戶端配置選項
創(chuàng)建客戶端連接時可以傳遞多種配置選項:
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)的特性,既可讀又可寫。
創(chuàng)建 Socket
除了服務器自動創(chuàng)建外,也可以手動創(chuàng)建:
const socket = new net.Socket();
socket.connect(3000, 'localhost', () => {
console.log('連接成功');
});
Socket 屬性
socket.remoteAddress: 遠程 IP 地址socket.remotePort: 遠程端口socket.localAddress: 本地 IP 地址socket.localPort: 本地端口socket.bytesRead: 接收的字節(jié)數(shù)socket.bytesWritten: 發(fā)送的字節(jié)數(shù)
socket.on('connect', () => {
console.log(`連接到 ${socket.remoteAddress}:${socket.remotePort}`);
console.log(`本地端口: ${socket.localPort}`);
});
Socket 方法
socket.write(data[, encoding][, callback]): 發(fā)送數(shù)據(jù)socket.end([data][, encoding][, callback]): 結(jié)束連接socket.destroy([error]): 強制關(guān)閉連接socket.pause(): 暫停數(shù)據(jù)讀取socket.resume(): 恢復數(shù)據(jù)讀取socket.setKeepAlive([enable][, initialDelay]): 設置 keepalivesocket.setNoDelay([noDelay]): 禁用 Nagle 算法
事件處理
服務器事件
server.on('listening', () => {
const address = server.address();
console.log(`服務器監(jiān)聽 ${address.address}:${address.port}`);
});
server.on('error', (err) => {
if (err.code === 'EADDRINUSE') {
console.error('端口已被占用');
}
});
Socket 事件
connect: 成功建立連接時觸發(fā)data: 接收到數(shù)據(jù)時觸發(fā)end: 對方結(jié)束發(fā)送數(shù)據(jù)時觸發(fā)timeout: 連接超時時觸發(fā)error: 發(fā)生錯誤時觸發(fā)close: 連接完全關(guān)閉時觸發(fā)
socket.on('data', (data) => {
console.log(`接收到數(shù)據(jù): ${data.toString()}`);
});
socket.on('timeout', () => {
console.log('連接超時');
socket.end();
});
socket.on('close', (hadError) => {
console.log(`連接關(guān)閉${hadError ? ',發(fā)生錯誤' : ''}`);
});
數(shù)據(jù)傳輸
發(fā)送數(shù)據(jù)
// 發(fā)送字符串
socket.write('Hello', 'utf8');
// 發(fā)送 Buffer
const buffer = Buffer.from([0x68, 0x65, 0x6c, 0x6c, 0x6f]);
socket.write(buffer);
// 使用回調(diào)確認數(shù)據(jù)已被發(fā)送
socket.write('World', () => {
console.log('數(shù)據(jù)已發(fā)送');
});
接收數(shù)據(jù)
let chunks = [];
socket.on('data', (chunk) => {
chunks.push(chunk);
});
socket.on('end', () => {
const data = Buffer.concat(chunks).toString();
console.log(`完整數(shù)據(jù): ${data}`);
});
處理二進制數(shù)據(jù)
socket.on('data', (chunk) => {
// 假設前兩個字節(jié)表示消息長度
const messageLength = chunk.readUInt16BE(0);
const message = chunk.slice(2, 2 + messageLength);
console.log(`消息內(nèi)容: ${message.toString()}`);
});
高級特性
IPC (進程間通信)
除了 TCP 通信,net 模塊也支持通過 Unix 域套接字或命名管道進行進程間通信:
// 服務器
const server = net.createServer().listen('/tmp/echo.sock');
// 客戶端
const client = net.createConnection({ path: '/tmp/echo.sock' });
多連接管理
實際應用中,服務器通常需要管理多個連接:
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} 已斷開,當前連接數(shù): ${connections.size}`);
});
});
// 向所有客戶端廣播消息
function broadcast(message) {
for (const socket of connections.values()) {
socket.write(message);
}
}
重連機制
客戶端斷線重連示例:
function createClient() {
const client = net.createConnection({ port: 3000 });
client.on('error', (err) => {
console.error('連接錯誤:', err);
});
client.on('close', () => {
console.log('連接關(guān)閉,嘗試重連...');
setTimeout(() => {
createClient();
}, 3000); // 3秒后重連
});
return client;
}
const client = createClient();
實際應用案例
簡單聊天服務器
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} 發(fā)生錯誤:`, err);
});
});
// 廣播消息給所有客戶端
function broadcast(message, sender) {
clients.forEach((client) => {
// 不發(fā)送給消息發(fā)送者
if (client !== sender) {
client.write(message);
}
});
console.log(message);
}
server.listen(3000, () => {
console.log('聊天服務器已啟動,監(jiān)聽端口 3000');
});
簡單的 HTTP 服務器
使用 net 模塊實現(xiàn)基礎 HTTP 服務器:
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/');
});
性能優(yōu)化
使用 Buffer 池
對于高性能應用,可以使用 Buffer 池避免頻繁創(chuàng)建新 Buffer:
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 發(fā)送數(shù)據(jù)
const dataToSend = "Hello";
const buffer = allocateBuffer(dataToSend.length);
buffer.write(dataToSend);
socket.write(buffer);
避免小包發(fā)送
合并小數(shù)據(jù)包可以提高網(wǎng)絡效率:
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;
}
調(diào)整 Socket 參數(shù)
針對不同場景優(yōu)化 Socket 設置:
// 低延遲應用 (禁用 Nagle 算法)
socket.setNoDelay(true);
// 長連接應用
socket.setKeepAlive(true, 60000); // 60秒
// 設置超時
socket.setTimeout(30000); // 30秒
socket.on('timeout', () => {
console.log('連接超時');
socket.end();
});
常見問題解答
Q: 如何處理 EADDRINUSE 錯誤?
A: 這個錯誤表示端口已被占用,可以通過以下方式處理:
server.on('error', (err) => {
if (err.code === 'EADDRINUSE') {
console.log('端口已被占用,嘗試其他端口...');
server.close();
server.listen(port + 1);
}
});
Q: 如何實現(xiàn)心跳機制?
A: 通過定時發(fā)送心跳包確保連接活躍:
// 服務端心跳檢測
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: 如何處理大量數(shù)據(jù)傳輸?
A: 使用流控制和數(shù)據(jù)分塊:
const fs = require('fs');
// 發(fā)送大文件
function sendLargeFile(socket, filePath) {
const fileStream = fs.createReadStream(filePath);
fileStream.on('data', (chunk) => {
// 檢查緩沖區(qū)是否已滿
const canContinue = socket.write(chunk);
if (!canContinue) {
// 如果緩沖區(qū)已滿,暫停讀取
fileStream.pause();
// 當緩沖區(qū)清空后,恢復讀取
socket.once('drain', () => {
fileStream.resume();
});
}
});
fileStream.on('end', () => {
console.log('文件發(fā)送完成');
});
}到此這篇關(guān)于Node.js net模塊的使用示例的文章就介紹到這了,更多相關(guān)Node.js net模塊內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
node.js降低版本的方式詳解(解決sass和node.js沖突問題)
這篇文章主要介紹了node.js降低版本的方式(解決sass和node.js沖突),本文是因為sass版本和node版本不匹配(可以找一下對應的版本),本文給大家詳細講解,需要的朋友可以參考下2023-02-02
Nodejs把接收圖片base64格式保存為文件存儲到服務器上
這篇文章主要介紹了Nodejs把接收圖片base64格式保存為文件存儲到服務器上,文中代碼較簡短,需要的朋友可以參考下2018-09-09

