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

Node.js SocketIO无法发送数据流的问题求助

Node.js SocketIO无法发送数据流的问题求助

兄弟我太懂你这种传大文件卡壳的郁闷了!之前我做类似的Node+TS项目时也踩过这个坑,咱们来一步步把问题捋明白,再给你两个可行的解决方案。

首先得搞清楚为啥你现在的代码不行:原生SocketIO的emit方法只能传输可JSON序列化的数据,而你直接传的file.stream()是一个复杂的Stream对象,SocketIO在序列化它的时候会直接转成空对象,这就是服务器收到空值的原因——它根本没法把Stream当成普通数据来传输。

接下来给你两种解决思路,你可以根据自己的需求选:


方案一:搞定socket.io-stream在ES模块下的使用

虽然socket.io-stream原本是CommonJS风格的,但完全可以适配ES模块(包括你的TS项目),只要调整一下导入和使用方式就行:

1. 先安装依赖

npm install socket.io-stream @types/socket.io-stream

2. 客户端代码(浏览器/Node ES模块)

import * as ss from 'socket.io-stream';
import { io } from 'socket.io-client';

// 建立Socket连接
const socket = io('http://你的服务器地址');
// 创建socket.io-stream专用的流对象
const uploadStream = ss.createStream();

// 用ss包装socket后再emit,同时可以附带文件名等元数据
ss(socket).emit('upload', uploadStream, { filename: file.name }, (response: any) => {
  console.log('上传结果:', response);
});

// 如果是浏览器环境,把File的ReadableStream转成socket.io-stream能识别的格式
const fileReadStream = file.stream();
await fileReadStream.pipeTo(new WritableStream({
  write(chunk) {
    uploadStream.write(chunk);
  },
  close() {
    uploadStream.end(); // 写完后关闭流
  }
}));

3. 服务器端代码(Node TS ES模块)

import { Server } from 'socket.io';
import * as ss from 'socket.io-stream';
import { createWriteStream } from 'fs';
import { join } from 'path';

const io = new Server(3000);

io.on('connection', (socket) => {
  // 同样用ss包装socket来监听流
  ss(socket).on('upload', (stream, metaData, callback) => {
    console.log('收到上传请求,文件名:', metaData.filename);
    // 创建本地文件写入流
    const savePath = join(__dirname, 'uploads', metaData.filename);
    const writeStream = createWriteStream(savePath);

    // 把socket传来的流管道到本地文件
    stream.pipe(writeStream);

    // 监听完成/错误事件,给客户端返回结果
    writeStream.on('finish', () => {
      callback({ status: 'success', message: '文件上传完成!' });
    });

    writeStream.on('error', (err) => {
      callback({ status: 'error', message: err.message });
    });
  });
});

方案二:手动分块传输(不用第三方库)

如果你不想依赖socket.io-stream,可以自己把大文件切成小块(比如1MB/块),分多次发送,服务器端再拼接起来,这种方式可控性更高:

1. 客户端代码

import { io } from 'socket.io-client';

const socket = io('http://你的服务器地址');
const file = /* 你的File对象,比如来自<input type="file"> */;
const chunkSize = 1024 * 1024; // 每块1MB
let currentOffset = 0;

// 递归读取并发送文件块
const sendChunk = async () => {
  // 切割文件
  const chunk = file.slice(currentOffset, currentOffset + chunkSize);
  const arrayBuffer = await chunk.arrayBuffer();
  const buffer = Buffer.from(arrayBuffer);

  // 发送块数据,附带元信息
  socket.emit('upload-chunk', {
    filename: file.name,
    chunk: buffer,
    offset: currentOffset,
    totalSize: file.size
  });

  currentOffset += chunkSize;

  // 没传完就继续下一块
  if (currentOffset < file.size) {
    sendChunk();
  } else {
    // 通知服务器传输结束
    socket.emit('upload-finish', { filename: file.name });
  }
};

// 监听服务器的完成响应
socket.on('upload-complete', (response) => {
  console.log('上传完成:', response);
});

// 开始上传
sendChunk();

2. 服务器端代码

import { Server } from 'socket.io';
import { createWriteStream, existsSync, mkdirSync } from 'fs';
import { join } from 'path';

// 创建上传目录
const uploadDir = join(__dirname, 'uploads');
if (!existsSync(uploadDir)) mkdirSync(uploadDir);

// 记录正在进行的上传任务
const activeUploads = new Map<string, { writeStream: any, receivedSize: number }>();

const io = new Server(3000);

io.on('connection', (socket) => {
  // 接收文件块
  socket.on('upload-chunk', (data) => {
    const { filename, chunk, offset, totalSize } = data;
    let uploadTask = activeUploads.get(filename);

    // 如果是第一次接收这个文件,创建写入流
    if (!uploadTask) {
      const writeStream = createWriteStream(join(uploadDir, filename), { flags: 'w' });
      uploadTask = { writeStream, receivedSize: 0 };
      activeUploads.set(filename, uploadTask);
    }

    // 写入块数据
    uploadTask.writeStream.write(chunk, (err: any) => {
      if (err) {
        console.error('写入块失败:', err);
        socket.emit('upload-error', { filename, message: err.message });
        activeUploads.delete(filename);
        uploadTask.writeStream.close();
        return;
      }

      uploadTask.receivedSize += chunk.length;
      // 检查是否已经接收完所有数据
      if (uploadTask.receivedSize === totalSize) {
        uploadTask.writeStream.end(() => {
          socket.emit('upload-complete', { status: 'success', message: '文件上传完成!' });
          activeUploads.delete(filename);
        });
      }
    });
  });

  // 处理客户端的结束通知
  socket.on('upload-finish', (data) => {
    const uploadTask = activeUploads.get(data.filename);
    if (uploadTask) {
      uploadTask.writeStream.end(() => {
        socket.emit('upload-complete', { status: 'success', message: '文件上传完成!' });
        activeUploads.delete(filename);
      });
    }
  });
});

两种方案各有优劣:用socket.io-stream更符合Stream的编程习惯,代码更简洁;手动分块则不需要依赖第三方库,对传输过程的控制更细,比如可以做进度条、断点续传之类的功能。你可以根据自己的项目需求来选~

备注:内容来源于stack exchange,提问作者Johan Daniel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.22 07:00:27