Node.js net模塊的使用示例
簡(jiǎn)介
Node.js 的 net
模塊提供了用于實(shí)現(xiàn) TCP 服務(wù)器和客戶端的異步網(wǎng)絡(luò) API。它是 Node.js 網(wǎng)絡(luò)功能的核心,為上層模塊如 HTTP、HTTPS 等提供了基礎(chǔ)支持。本教程將全面介紹 net
模塊的使用方法和最佳實(shí)踐。
引入 net 模塊
const net = require('net');
核心概念
TCP (傳輸控制協(xié)議)
TCP 是一種面向連接的、可靠的、基于字節(jié)流的傳輸層通信協(xié)議。net
模塊主要處理 TCP 通信。
Socket
Socket 是網(wǎng)絡(luò)通信的端點(diǎn),在 Node.js 中表示為 net.Socket
類的實(shí)例。它可以是服務(wù)器與客戶端之間建立的連接,也可以是客戶端主動(dòng)創(chuàng)建的連接。
服務(wù)器
服務(wù)器使用 net.Server
類創(chuàng)建,負(fù)責(zé)監(jiān)聽(tīng)連接并處理客戶端請(qǐng)求。
TCP 服務(wù)器創(chuàng)建
基本服務(wù)器
const net = require('net'); // 創(chuàng)建服務(wù)器 const server = net.createServer((socket) => { console.log('客戶端已連接'); // 接收數(shù)據(jù) socket.on('data', (data) => { console.log(`接收到數(shù)據(jù): ${data}`); // 發(fā)送響應(yīng) socket.write('服務(wù)器已收到你的消息'); }); // 連接關(guān)閉 socket.on('end', () => { console.log('客戶端已斷開(kāi)連接'); }); // 處理錯(cuò)誤 socket.on('error', (err) => { console.error('連接錯(cuò)誤:', err); }); }); // 監(jiān)聽(tīng)端口 server.listen(3000, () => { console.log('服務(wù)器啟動(dòng)成功,監(jiān)聽(tīng)端口 3000'); });
服務(wù)器配置選項(xiàng)
創(chuàng)建服務(wù)器時(shí)可以傳遞配置選項(xiàng):
const server = net.createServer({ allowHalfOpen: false, // 當(dāng)另一端發(fā)送 FIN 包時(shí)自動(dòng)發(fā)送 FIN (默認(rèn)) pauseOnConnect: false // 是否在連接時(shí)暫停套接字 (默認(rèn)) });
服務(wù)器事件
net.Server
類繼承自 EventEmitter
,支持以下主要事件:
listening
: 服務(wù)器開(kāi)始監(jiān)聽(tīng)連接時(shí)觸發(fā)connection
: 新客戶端連接建立時(shí)觸發(fā)error
: 發(fā)生錯(cuò)誤時(shí)觸發(fā)close
: 服務(wù)器關(guān)閉時(shí)觸發(fā)
server.on('listening', () => { console.log('服務(wù)器開(kāi)始監(jiān)聽(tīng)連接'); }); server.on('connection', (socket) => { console.log('新客戶端連接'); }); server.on('error', (err) => { console.error('服務(wù)器錯(cuò)誤:', err); }); server.on('close', () => { console.log('服務(wù)器已關(guān)閉'); });
TCP 客戶端創(chuàng)建
基本客戶端
const net = require('net'); // 創(chuàng)建連接 const client = net.createConnection({ host: 'localhost', port: 3000 }, () => { console.log('已連接到服務(wù)器'); // 發(fā)送數(shù)據(jù) client.write('你好,服務(wù)器'); }); // 接收數(shù)據(jù) client.on('data', (data) => { console.log(`接收到服務(wù)器響應(yīng): ${data}`); // 關(guān)閉連接 client.end(); }); // 連接結(jié)束 client.on('end', () => { console.log('已斷開(kāi)與服務(wù)器的連接'); }); // 錯(cuò)誤處理 client.on('error', (err) => { console.error('連接錯(cuò)誤:', err); });
客戶端配置選項(xiàng)
創(chuàng)建客戶端連接時(shí)可以傳遞多種配置選項(xiàng):
const client = net.createConnection({ host: 'localhost', // 主機(jī)名 port: 3000, // 端口號(hào) localAddress: '192.168.1.100', // 本地接口 family: 4, // IP 版本 (4 或 6) timeout: 5000 // 連接超時(shí)(毫秒) });
Socket 對(duì)象
net.Socket
是 TCP 連接的抽象,具有流(Duplex Stream)的特性,既可讀又可寫(xiě)。
創(chuàng)建 Socket
除了服務(wù)器自動(dòng)創(chuàng)建外,也可以手動(dòng)創(chuàng)建:
const socket = new net.Socket(); socket.connect(3000, 'localhost', () => { console.log('連接成功'); });
Socket 屬性
socket.remoteAddress
: 遠(yuǎn)程 IP 地址socket.remotePort
: 遠(yuǎn)程端口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])
: 強(qiáng)制關(guān)閉連接socket.pause()
: 暫停數(shù)據(jù)讀取socket.resume()
: 恢復(fù)數(shù)據(jù)讀取socket.setKeepAlive([enable][, initialDelay])
: 設(shè)置 keepalivesocket.setNoDelay([noDelay])
: 禁用 Nagle 算法
事件處理
服務(wù)器事件
server.on('listening', () => { const address = server.address(); console.log(`服務(wù)器監(jiān)聽(tīng) ${address.address}:${address.port}`); }); server.on('error', (err) => { if (err.code === 'EADDRINUSE') { console.error('端口已被占用'); } });
Socket 事件
connect
: 成功建立連接時(shí)觸發(fā)data
: 接收到數(shù)據(jù)時(shí)觸發(fā)end
: 對(duì)方結(jié)束發(fā)送數(shù)據(jù)時(shí)觸發(fā)timeout
: 連接超時(shí)時(shí)觸發(fā)error
: 發(fā)生錯(cuò)誤時(shí)觸發(fā)close
: 連接完全關(guān)閉時(shí)觸發(fā)
socket.on('data', (data) => { console.log(`接收到數(shù)據(jù): ${data.toString()}`); }); socket.on('timeout', () => { console.log('連接超時(shí)'); socket.end(); }); socket.on('close', (hadError) => { console.log(`連接關(guān)閉${hadError ? ',發(fā)生錯(cuò)誤' : ''}`); });
數(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)確認(rèn)數(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}`); });
處理二進(jìn)制數(shù)據(jù)
socket.on('data', (chunk) => { // 假設(shè)前兩個(gè)字節(jié)表示消息長(zhǎng)度 const messageLength = chunk.readUInt16BE(0); const message = chunk.slice(2, 2 + messageLength); console.log(`消息內(nèi)容: ${message.toString()}`); });
高級(jí)特性
IPC (進(jìn)程間通信)
除了 TCP 通信,net
模塊也支持通過(guò) Unix 域套接字或命名管道進(jìn)行進(jìn)程間通信:
// 服務(wù)器 const server = net.createServer().listen('/tmp/echo.sock'); // 客戶端 const client = net.createConnection({ path: '/tmp/echo.sock' });
多連接管理
實(shí)際應(yīng)用中,服務(wù)器通常需要管理多個(gè)連接:
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} 已斷開(kāi),當(dāng)前連接數(shù): ${connections.size}`); }); }); // 向所有客戶端廣播消息 function broadcast(message) { for (const socket of connections.values()) { socket.write(message); } }
重連機(jī)制
客戶端斷線重連示例:
function createClient() { const client = net.createConnection({ port: 3000 }); client.on('error', (err) => { console.error('連接錯(cuò)誤:', err); }); client.on('close', () => { console.log('連接關(guān)閉,嘗試重連...'); setTimeout(() => { createClient(); }, 3000); // 3秒后重連 }); return client; } const client = createClient();
實(shí)際應(yīng)用案例
簡(jiǎn)單聊天服務(wù)器
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(`歡迎來(lái)到聊天室,${socket.name}!\n`); // 接收消息 socket.on('data', (data) => { broadcast(`${socket.name}: ${data}`, socket); }); // 斷開(kāi)連接 socket.on('end', () => { clients.splice(clients.indexOf(socket), 1); broadcast(`${socket.name} 已離開(kāi)聊天室`, socket); }); // 處理錯(cuò)誤 socket.on('error', (err) => { console.error(`${socket.name} 發(fā)生錯(cuò)誤:`, err); }); }); // 廣播消息給所有客戶端 function broadcast(message, sender) { clients.forEach((client) => { // 不發(fā)送給消息發(fā)送者 if (client !== sender) { client.write(message); } }); console.log(message); } server.listen(3000, () => { console.log('聊天服務(wù)器已啟動(dòng),監(jiān)聽(tīng)端口 3000'); });
簡(jiǎn)單的 HTTP 服務(wù)器
使用 net
模塊實(shí)現(xiàn)基礎(chǔ) HTTP 服務(wù)器:
const net = require('net'); const server = net.createServer((socket) => { socket.on('data', (data) => { const request = data.toString(); console.log('收到請(qǐng)求:', request); // 簡(jiǎn)單的 HTTP 響應(yīng) 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 錯(cuò)誤:', err); }); }); server.listen(8080, () => { console.log('HTTP 服務(wù)器運(yùn)行在 http://localhost:8080/'); });
性能優(yōu)化
使用 Buffer 池
對(duì)于高性能應(yīng)用,可以使用 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; } // 使用預(yù)分配的 buffer 發(fā)送數(shù)據(jù) const dataToSend = "Hello"; const buffer = allocateBuffer(dataToSend.length); buffer.write(dataToSend); socket.write(buffer);
避免小包發(fā)送
合并小數(shù)據(jù)包可以提高網(wǎng)絡(luò)效率:
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ù)
針對(duì)不同場(chǎng)景優(yōu)化 Socket 設(shè)置:
// 低延遲應(yīng)用 (禁用 Nagle 算法) socket.setNoDelay(true); // 長(zhǎng)連接應(yīng)用 socket.setKeepAlive(true, 60000); // 60秒 // 設(shè)置超時(shí) socket.setTimeout(30000); // 30秒 socket.on('timeout', () => { console.log('連接超時(shí)'); socket.end(); });
常見(jiàn)問(wèn)題解答
Q: 如何處理 EADDRINUSE 錯(cuò)誤?
A: 這個(gè)錯(cuò)誤表示端口已被占用,可以通過(guò)以下方式處理:
server.on('error', (err) => { if (err.code === 'EADDRINUSE') { console.log('端口已被占用,嘗試其他端口...'); server.close(); server.listen(port + 1); } });
Q: 如何實(shí)現(xiàn)心跳機(jī)制?
A: 通過(guò)定時(shí)發(fā)送心跳包確保連接活躍:
// 服務(wù)端心跳檢測(cè) 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秒沒(méi)有心跳,斷開(kāi)連接 if (now - client.lastHeartbeat > 30000) { console.log(`客戶端 ${id} 心跳超時(shí),斷開(kāi)連接`); 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(); // 當(dāng)緩沖區(qū)清空后,恢復(fù)讀取 socket.once('drain', () => { fileStream.resume(); }); } }); fileStream.on('end', () => { console.log('文件發(fā)送完成'); }); }
到此這篇關(guān)于Node.js net模塊的使用示例的文章就介紹到這了,更多相關(guān)Node.js net模塊內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于nodejs 的多頁(yè)面爬蟲(chóng)實(shí)例代碼
本篇文章主要介紹了基于nodejs 的多頁(yè)面爬蟲(chóng) ,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-05-05windows實(shí)現(xiàn)npm和cnpm安裝步驟
這篇文章主要介紹了windows實(shí)現(xiàn)npm和cnpm安裝步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10基于Koa2寫(xiě)個(gè)腳手架模擬接口服務(wù)的方法
這篇文章主要介紹了基于Koa2寫(xiě)個(gè)腳手架模擬接口服務(wù)的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-11-11node.js中的fs.fchmodSync方法使用說(shuō)明
這篇文章主要介紹了node.js中的fs.fchmodSync方法使用說(shuō)明,本文介紹了fs.fchmodSync的方法說(shuō)明、語(yǔ)法、接收參數(shù)、使用實(shí)例和實(shí)現(xiàn)源碼,需要的朋友可以參考下2014-12-12node.js降低版本的方式詳解(解決sass和node.js沖突問(wèn)題)
這篇文章主要介紹了node.js降低版本的方式(解決sass和node.js沖突),本文是因?yàn)閟ass版本和node版本不匹配(可以找一下對(duì)應(yīng)的版本),本文給大家詳細(xì)講解,需要的朋友可以參考下2023-02-02Nodejs把接收?qǐng)D片base64格式保存為文件存儲(chǔ)到服務(wù)器上
這篇文章主要介紹了Nodejs把接收?qǐng)D片base64格式保存為文件存儲(chǔ)到服務(wù)器上,文中代碼較簡(jiǎn)短,需要的朋友可以參考下2018-09-09