如何用Node.js的net模块实现TCP服务器实时图片传输?
基于Node.js net模块实现TCP实时图片传输方案
要在TCP聊天系统中添加图片传输功能,核心是解决TCP字节流的粘包问题,并定义一套简单的通信协议来区分文本和图片数据。以下是具体实现方案和代码示例:
核心思路
TCP是无边界的字节流,无法直接区分不同数据包的边界,因此需要自定义通信协议:
- 每个数据包分为头部和内容两部分
- 头部用JSON格式标识数据类型(文本/图片)和内容字节长度
- 用固定分隔符(如
||)分隔头部与内容,接收端通过解析头部来确定后续要读取的字节数
服务器端代码
const net = require('net'); const fs = require('fs'); const server = net.createServer(); const clients = new Set(); server.on('connection', (socket) => { console.log('新客户端已连接'); clients.add(socket); // 维护接收缓冲区,处理粘包 let receiveBuffer = Buffer.alloc(0); socket.on('data', (chunk) => { receiveBuffer = Buffer.concat([receiveBuffer, chunk]); // 循环解析完整数据包 while (true) { const separatorIndex = receiveBuffer.indexOf('||'); if (separatorIndex === -1) break; // 解析头部 const headerStr = receiveBuffer.slice(0, separatorIndex).toString(); let header; try { header = JSON.parse(headerStr); } catch (err) { console.error('头部解析失败,丢弃无效数据'); receiveBuffer = receiveBuffer.slice(separatorIndex + 2); continue; } // 检查是否已接收完整内容 if (receiveBuffer.length < separatorIndex + 2 + header.length) break; // 提取内容并更新缓冲区 const content = receiveBuffer.slice(separatorIndex + 2, separatorIndex + 2 + header.length); receiveBuffer = receiveBuffer.slice(separatorIndex + 2 + header.length); // 根据数据类型处理 if (header.type === 'text') { const message = content.toString(); console.log(`收到文本消息: ${message}`); // 广播给其他客户端 clients.forEach(client => client !== socket && client.write(`${headerStr}||${content}`)); } else if (header.type === 'image') { console.log('收到图片数据'); // 广播图片给其他客户端 clients.forEach(client => client !== socket && client.write(`${headerStr}||${content}`)); // 可选:服务器本地保存图片 fs.writeFileSync(`server_received_${Date.now()}.png`, content); } } }); socket.on('end', () => { console.log('客户端已断开连接'); clients.delete(socket); }); socket.on('error', (err) => { console.error('客户端连接出错:', err); clients.delete(socket); }); }); server.listen(3000, () => { console.log('TCP聊天服务器运行在端口3000'); });
客户端代码
const net = require('net'); const fs = require('fs'); const readline = require('readline'); const client = net.connect({ port: 3000 }, () => { console.log('已连接到服务器'); }); // 处理服务器推送的数据 let receiveBuffer = Buffer.alloc(0); client.on('data', (chunk) => { receiveBuffer = Buffer.concat([receiveBuffer, chunk]); while (true) { const separatorIndex = receiveBuffer.indexOf('||'); if (separatorIndex === -1) break; const headerStr = receiveBuffer.slice(0, separatorIndex).toString(); let header; try { header = JSON.parse(headerStr); } catch (err) { console.error('头部解析失败'); receiveBuffer = receiveBuffer.slice(separatorIndex + 2); continue; } if (receiveBuffer.length < separatorIndex + 2 + header.length) break; const content = receiveBuffer.slice(separatorIndex + 2, separatorIndex + 2 + header.length); receiveBuffer = receiveBuffer.slice(separatorIndex + 2 + header.length); if (header.type === 'text') { console.log(`收到消息: ${content.toString()}`); } else if (header.type === 'image') { const filename = `client_received_${Date.now()}.png`; fs.writeFileSync(filename, content); console.log(`图片已保存为 ${filename}`); } } }); client.on('end', () => { console.log('与服务器断开连接'); }); // 命令行交互逻辑 const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); rl.on('line', (input) => { if (input.startsWith('/sendimage ')) { // 发送图片命令格式:/sendimage 图片路径 const filePath = input.slice(11).trim(); try { const imageBuffer = fs.readFileSync(filePath); const header = JSON.stringify({ type: 'image', length: imageBuffer.length }); client.write(`${header}||${imageBuffer}`); console.log('图片发送成功'); } catch (err) { console.error('读取图片失败:', err.message); } } else { // 发送文本消息 const textBuffer = Buffer.from(input); const header = JSON.stringify({ type: 'text', length: textBuffer.length }); client.write(`${header}||${textBuffer}`); } });
关键细节说明
- 粘包处理:通过维护接收缓冲区,循环解析完整数据包,确保不会将多个数据包混在一起处理
- 协议扩展性:如果需要支持更多数据类型(如文件、语音),只需在头部的
type字段添加新标识即可 - 大文件优化:示例适合小图片传输,若需传输大文件,可将图片拆分为多个分块发送,接收端拼接后保存
内容的提问来源于stack exchange,提问作者user23497693
相关产品推荐
相关产品推荐

