如何通过Websocket传输文件并附带额外信息?
高效实现Websocket带元数据的二进制文件传输方案
我之前做过类似的Websocket文件传输需求,给你几个实用的方案,完全可以替代低效的base64方式,而且能轻松带上目标客户端这类元数据:
方案1:拆分消息顺序发送(简单易实现)
核心思路是先发送JSON格式的元数据,再发送二进制文件内容,服务端缓存元数据后,结合二进制数据转发给目标客户端。
客户端(管理界面)代码示例
// 监听文件选择事件 document.getElementById('filePicker').addEventListener('change', (e) => { const file = e.target.files[0]; if (!file) return; const socket = yourExistingWsConnection; // 1. 先发送元数据:包含目标客户端、资源类型等信息 socket.send(JSON.stringify({ type: 'media_file', targetClients: ['monitor_1', 'monitor_2'], // 替换成你的客户端标识 filename: file.name, mimeType: file.type })); // 2. 直接发送二进制File对象 socket.send(file); });
服务端(Node.js ws库)代码示例
const WebSocket = require('ws'); const wss = new WebSocket.Server({ port: 8080 }); // 缓存待匹配的文件元数据 let pendingFileMeta = null; wss.on('connection', (ws) => { // 给每个客户端分配唯一标识(比如从请求参数或Cookie获取) ws.clientId = ws.upgradeReq.query.clientId; ws.on('message', (data) => { if (typeof data === 'string') { // 收到元数据,缓存起来 pendingFileMeta = JSON.parse(data); } else { // 收到二进制文件,结合元数据转发 if (!pendingFileMeta) return; // 遍历所有客户端,转发给目标对象 wss.clients.forEach((client) => { if (pendingFileMeta.targetClients.includes(client.clientId) && client.readyState === WebSocket.OPEN) { // 先给目标客户端发元数据,再发二进制文件 client.send(JSON.stringify(pendingFileMeta)); client.send(data); } }); // 清空缓存,避免干扰下一次传输 pendingFileMeta = null; } }); });
方案2:自定义二进制帧格式(高效单次传输)
如果想减少网络请求次数,可以自定义一个二进制帧协议:前N字节存储元数据的长度,后面紧跟JSON序列化的元数据,最后是文件二进制内容。服务端解析头部后就能拿到元数据,再提取文件内容转发。
客户端代码示例
document.getElementById('filePicker').addEventListener('change', (e) => { const file = e.target.files[0]; if (!file) return; const socket = yourExistingWsConnection; const metadata = { type: 'media_file', targetClients: ['monitor_1', 'monitor_2'], filename: file.name, mimeType: file.type }; // 将元数据转为Buffer const metaStr = JSON.stringify(metadata); const metaBuffer = Buffer.from(metaStr); // 用前4字节存储元数据长度(Uint32格式,支持最大4GB的元数据,完全够用) const lengthBuffer = new ArrayBuffer(4); new Uint32Array(lengthBuffer)[0] = metaBuffer.length; // 读取文件为ArrayBuffer,合并所有数据 const reader = new FileReader(); reader.onload = () => { const fileBuffer = new Uint8Array(reader.result); const combinedBuffer = new Uint8Array( lengthBuffer.byteLength + metaBuffer.length + fileBuffer.length ); // 拼接:长度 -> 元数据 -> 文件内容 combinedBuffer.set(new Uint8Array(lengthBuffer), 0); combinedBuffer.set(metaBuffer, lengthBuffer.byteLength); combinedBuffer.set(fileBuffer, lengthBuffer.byteLength + metaBuffer.length); // 发送复合二进制数据 socket.send(combinedBuffer); }; reader.readAsArrayBuffer(file); });
服务端代码示例
wss.on('connection', (ws) => { ws.clientId = ws.upgradeReq.query.clientId; ws.on('message', (data) => { if (!(data instanceof Buffer)) return; // 1. 读取前4字节的元数据长度 const metaLength = new Uint32Array(data.slice(0, 4).buffer)[0]; // 2. 读取元数据内容 const metaStr = data.slice(4, 4 + metaLength).toString(); const metadata = JSON.parse(metaStr); // 3. 提取文件二进制数据 const fileBuffer = data.slice(4 + metaLength); // 转发给目标客户端 wss.clients.forEach((client) => { if (metadata.targetClients.includes(client.clientId) && client.readyState === WebSocket.OPEN) { // 可以直接发送整个复合包,客户端再解析;也可以拆分发送元数据+文件 client.send(data); } }); }); });
方案对比
- 方案1:实现成本极低,不需要处理复杂的二进制拼接,适合快速开发或小文件场景。
- 方案2:单次传输完成,减少网络交互 overhead,更适合大文件或对性能要求高的场景。
这两个方案都完全避开了base64的性能问题,而且不需要依赖typeson、BSON这类第三方库,用原生API就能搞定。
内容的提问来源于stack exchange,提问作者user2859982
相关产品推荐
相关产品推荐

