You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 07:50:33