Node.js单Socket连接多文件流传输:服务端如何区分不同流?
如何在单Socket连接的多文件流中添加标识让服务端区分
当然可以解决这个问题!核心思路就是给每个文件流加上专属的标识帧,让服务端能清楚区分不同文件的起始、内容和结束。下面我给你具体的实现方案和代码示例,都是可直接参考的:
客户端实现:给每个文件流添加标识
我们需要在传输每个文件前,先发送包含文件唯一ID、文件名、大小的头部标识;文件内容传输完成后,再发送结束标识。这样服务端就能精准识别每个文件的边界。
const net = require('net'); const fs = require('fs'); const { pipeline } = require('stream/promises'); // 封装发送单个文件的函数 async function sendFile(client, fileInfo) { // 1. 发送文件头部标识(用JSON序列化+分隔符区分) const header = JSON.stringify({ type: 'file-start', fileId: fileInfo.id, fileName: fileInfo.name, fileSize: fs.statSync(fileInfo.path).size }) + '|||'; // 用|||作为标识和文件内容的分隔符 client.write(header); // 2. 传输文件内容(end: false 避免传输完一个文件就关闭连接) const fileStream = fs.createReadStream(fileInfo.path); await pipeline(fileStream, client, { end: false }); // 3. 发送文件结束标识 const footer = JSON.stringify({ type: 'file-end', fileId: fileInfo.id }) + '|||'; client.write(footer); } // 创建Socket客户端连接 const client = net.createConnection({ allowHalfOpen: true, host: 'xxxxx', port: xxxxx }); client.on('connect', async () => { // 串行发送两个文件(串行更简单,避免数据混叠;若要并行需给每个chunk加标识,实现更复杂) await sendFile(client, { id: 'file-001', name: '1gb.txt', path: '1gb.txt' }); await sendFile(client, { id: 'file-002', name: '2gb.txt', path: '2gb.txt' }); // 所有文件发送完毕后关闭连接 client.end(); });
服务端实现:解析标识并区分文件流
服务端需要维护缓冲区处理TCP粘包问题,解析每个标识帧后,对应创建/关闭文件写入流,把后续数据导向正确的文件。
const net = require('net'); const fs = require('fs'); net.createServer((netSocket) => { let buffer = ''; // 用来缓存未解析的数据流 let currentFile = null; // 当前正在接收的文件信息 let writeStream = null; // 当前文件的写入流 netSocket.on('data', (data) => { buffer += data.toString(); // 循环解析缓冲区中的数据,直到没有完整的标识帧 while (true) { const separatorIndex = buffer.indexOf('|||'); if (separatorIndex === -1) break; // 没有完整标识,退出循环 // 提取并解析标识帧 const frameStr = buffer.slice(0, separatorIndex); buffer = buffer.slice(separatorIndex + 3); // 移除已解析的内容和分隔符 try { const frame = JSON.parse(frameStr); if (frame.type === 'file-start') { // 开始接收新文件 currentFile = frame; writeStream = fs.createWriteStream(`received_${currentFile.fileName}`); console.log(`开始接收文件:${currentFile.fileName}(ID:${currentFile.fileId})`); } else if (frame.type === 'file-end') { // 文件接收完成,关闭写入流 if (writeStream) { writeStream.end(); console.log(`文件接收完成:${currentFile.fileName}(ID:${frame.fileId})`); currentFile = null; writeStream = null; } } } catch (err) { // 解析失败,说明这部分是文件内容(因为文件内容可能包含JSON无法解析的字符) if (currentFile && writeStream) { writeStream.write(frameStr); // 直接写入当前文件 } console.error('解析标识帧失败:', err); } // 处理缓冲区中剩余的文件内容(如果当前正在接收文件) if (currentFile && writeStream && buffer.length > 0) { const nextSeparator = buffer.indexOf('|||'); if (nextSeparator !== -1) { // 缓冲区中有文件内容和下一个标识帧,先写入文件内容 const fileData = buffer.slice(0, nextSeparator); writeStream.write(fileData); buffer = buffer.slice(nextSeparator); // 把标识帧留在缓冲区下次解析 } else { // 缓冲区全部是文件内容,直接写入 writeStream.write(buffer); buffer = ''; } } } }); netSocket.on('end', () => { // 客户端关闭连接时,确保未完成的文件流收尾 if (writeStream) { writeStream.end(); console.log('客户端连接关闭,剩余文件已收尾'); } }); }).listen(xxxxx, 'xxxxx');
关键注意事项
- TCP粘包处理:TCP是流式协议,数据可能被拆包或粘包,必须用分隔符或固定长度头部区分标识和文件内容,示例中用
|||作为分隔符 - 分隔符选择:要选不会在文件内容中出现的字符组合,若担心冲突,可以改用固定长度头部(比如先发送4字节表示标识长度,再发送标识内容)
- 传输方式选择:串行传输实现简单不易出错;若要并行传输,需要给每个数据chunk都带上
fileId标识,服务端根据ID分发给对应文件流,实现复杂度更高 - 完整性校验:建议在传输前后添加MD5等校验逻辑,确保文件传输没有损坏或丢失
内容的提问来源于stack exchange,提问作者Interface
相关产品推荐
相关产品推荐

