应分段传输二进制图片而非base64,因后者使帧体积增33%、易触发413错误且编解码开销大;须用blob或arraybuffer直接发送,加uuid与偏移量帧头,服务端按序重组并及时清理缓存。
别用 base64 发大图——它会让 websocket 帧体积膨胀 33%,极易触发 413 request entity too large 或连接中断,且前后端都要多做一次编解码。真正可行的路径是:分段传二进制 + 前端加帧头 + 服务端按序重组。
WebSocket.send() 只认这四种类型,Base64 是伪二进制
send() 接收且仅接收:string、Blob、ArrayBuffer、ArrayBufferView(如 Uint8Array)。Base64 字符串属于 string 类型,浏览器不会自动识别为图片数据,后端必须手动调用 Buffer.from(base64, 'base64') 解码——这一步在大图场景下极易 OOM。
- 用户选图后,
input.files[0]就是Blob,直接socket.send(file)即可,零拷贝 - 截图或 Canvas 处理场景,用
canvas.toBlob(callback, 'image/webp', 0.7),比toDataURL()内存占用低一个数量级 - 若需加协议头(如 4 字节长度 + 1 字节类型),先
blob.arrayBuffer(),再用Uint8Array拼接,最后socket.send(mergedBuffer) - 绝对不要把
toDataURL()结果再包一层 JSON:JSON.stringify({img: dataURL})——等于双重字符串化,纯属自找麻烦
大图必须分片:64KB/片 + UUID + 序号帧头
单帧超 500KB 容易阻塞连接、触发代理层截断(如 Nginx 默认 client_max_body_size 1m)。分片不是为了“兼容老浏览器”,而是规避 TCP 层粘包和 WebSocket 帧缓冲上限。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 前端生成唯一 ID:
crypto.randomUUID()(现代浏览器)或降级用时间戳+随机数拼接 - 每片加固定帧头:前 4 字节为
Uint32Array表示本片偏移量,第 5 字节为类型(0x02表示图片),后续为原始二进制数据 - 分片大小建议 64KB(
65536字节),太小增加帧头开销,太大仍可能被中间件拦截 - 发送完最后一片后,额外发一帧控制消息:
{type: 'end', uuid: 'xxx', totalSize: 1234567},通知服务端可以合并
服务端接收时最容易漏掉的三件事
Node.js 的 ws 库默认支持 Buffer,但你得主动管理分片状态。很多项目卡在这里:前端分了片,服务端却当成独立文件处理,结果图片乱码或缺失。
- 必须用内存缓存(如
Map)按uuid存储未完成的分片数组,不能存在临时变量里——WebSocket 连接复用时会覆盖 - 收到每片后,检查
event.data类型:如果是Buffer,直接 push;如果是string,说明前端没设binaryType = 'arraybuffer',要立刻报错而不是静默丢弃 - 拼接时用
Buffer.concat(chunks),别用chunks.join('')——后者只对字符串有效,对 Buffer 会转成[object Object] - 重组完成后,立刻从缓存中
delete对应uuid,否则内存泄漏
分片逻辑本身不难,难的是帧头设计与服务端状态清理的时机。UUID 要在前端生成并随每片携带,服务端绝不自己生成;帧头长度字段必须用网络字节序(new DataView(buffer).setUint32(0, offset, false)),否则跨平台解析错位。这些细节不写死在代码里,上线后基本靠日志盲猜。










