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

如何通过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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 09:23:30