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
相关产品推荐
相关产品推荐

