WebSocket不支持直接文件上传,需分片+自定义协议:前端切片(32–128KB)、附元数据发送;后端缓存分片、校验顺序、超时清理;进度由客户端计算,须防抖;断点续传需手写协议并前后端对齐。

WebSocket 本身不支持文件上传,必须分片 + 自定义协议
WebSocket 是全双工通信通道,但协议层面没有文件传输语义。直接把 File 对象丢进 ws.send() 会失败——浏览器会报 Failed to execute 'send' on 'WebSocket': InvalidStateError 或静默截断。真正可行的路径是:前端读取文件为 Blob / ArrayBuffer,手动切片(如每 64KB),每片带上元数据(fileId、chunkIndex、totalChunks、filename),再通过 WebSocket 发送二进制帧。
后端需维护上传上下文(如用 Map 缓存未完成的 fileId → chunks[]),收到最后一片后拼接并落盘。漏掉元数据字段或服务端未做超时清理,会导致内存泄漏或上传卡死。
- 切片大小建议 32–128 KB:太小增加帧头开销和往返压力;太大可能触发代理(如 Nginx)的 WebSocket 消息长度限制(默认 1MB)
- 必须加
fileId = Date.now() + '-' + Math.random().toString(36).substr(2, 9)避免并发上传冲突 - 服务端收到非最后一片时,不要立即写磁盘,只暂存内存或 Redis;否则大文件上传中断会造成碎片文件残留
进度条依赖客户端主动计算,服务端无法直接推送“已写入磁盘百分比”
很多人误以为服务端能实时告诉前端“磁盘写入进度”,其实不能。WebSocket 上传进度只能靠客户端自己算:每成功发送一片,就更新 uploadedBytes += chunk.byteLength,再除以 file.size 得到当前进度。服务端即使立刻返回 { type: 'chunk_ack', index: 5 },也只是确认“收到第 5 片”,不代表它已落盘。
如果需要服务端反馈处理状态(比如病毒扫描中、转码中),那是另一个独立消息流,和上传进度无关。混淆这两者会导致 UI 显示逻辑混乱——例如进度条卡在 99%,实际是服务端在做后续处理。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 务必监听
ws.onmessage中的chunk_ack类型消息,校验index连续性,防止丢包导致进度跳变 - 上传暂停/恢复需靠客户端记录已发送
maxIndex,重连后从下一片开始,服务端要支持跳过已收片段(查chunks数组长度) - 不要用
uploadProgress = (receivedChunks / totalChunks) * 100简单计算——网络抖动可能导致 ACK 延迟,UI 会来回跳,应加防抖或平滑插值
文本消息与文件分片必须共用同一连接,但需严格区分消息类型
一个 WebSocket 连接既要传文件分片,又要发聊天消息、错误通知、上传完成事件,就必须设计轻量消息封装格式。推荐用 JSON 前缀标识类型,二进制分片则用 ArrayBuffer 直传(不 JSON 序列化):
{"type":"text","from":"user1","content":"hello"}而分片用原生二进制帧,开头 4 字节为 fileId 长度,接着是 fileId 字符串,再是 4 字节 chunkIndex,最后是原始字节。这样避免把 10MB 文件塞进 JSON 字符串引发解析崩溃或内存暴涨。
- 服务端收到非二进制帧,先
JSON.parse()解析type;收到ArrayBuffer,按固定偏移读元数据,再截取 payload - 禁止在分片帧里混入 UTF-8 文本头(如
"CHUNK|xxx|1|5"),会浪费带宽且难以对齐边界 - 客户端发送前检查
ws.readyState === WebSocket.OPEN,否则send()抛异常,需降级为轮询或提示重连
真实环境必须处理断网重连、分片乱序、服务端重启等边界
开发时本地网络稳定,上线后用户切 4G、锁屏、浏览器挂起,都会导致连接中断。此时仅靠 ws.onclose 触发重连不够——分片可能只发了一半,服务端缓存也随进程退出丢失。可靠方案是:客户端上传前先发 {"type":"upload_init","fileId":"abc","size":10485760,"md5":"..."},服务端返回是否已有同 fileId 的上传记录(Redis 存 key: upload:abc:status)。有则返回已接收字节数,客户端跳过已传分片;无则新建。
- 服务端存储分片时,key 命名为
upload:abc:chunk:5,过期时间设为 24 小时,避免垃圾堆积 - 客户端用
setTimeout实现指数退避重连(1s → 2s → 4s),连续 5 次失败后提示“网络异常,请稍后重试” - 分片发送顺序不保证到达顺序(尤其跨 CDN 节点),服务端必须按
chunkIndex排序合并,不能依赖接收顺序
最易被忽略的是:WebSocket 上传无法像 HTTP 那样利用浏览器原生断点续传(Range 请求头)和服务器 206 Partial Content 响应。所有续传逻辑必须手写,且前后端协议必须对齐——差一个字段,整个流程就卡住。

















