websocket本身不支持大文件直接传输,因单帧有隐式大小限制;必须前端分片(带offset元数据)、后端幂等写入、前后端哈希校验与重连状态同步,才能实现可靠上传。

WebSocket 本身不支持“大文件传输”这个概念——它只传帧(frame),而帧有隐式大小限制。所谓“传大文件”,本质是前端切片 + WebSocket 送二进制帧 + 后端拼接,中间必须自己管 offset、校验、重连同步。直接 send(fileArrayBuffer) 传 200MB,iOS 会杀进程,Node.js 服务端可能静默断连,且无法暂停/续传。
为什么不能直接 send() 整个 ArrayBuffer
多数 WebSocket 实现对单帧有默认缓冲上限:Tomcat 是 8KB,uWebSockets.js 默认 16MB(可调但不推荐超),而 iOS 的 SRWebSocket 或 NSURLSessionWebSocketTask 会把整块 NSData 加载进内存,不是流式处理。
- 200MB 文件调
send()→ 内存峰值飙升,iOS 触发 JetsamEvent 杀后台进程 - 服务端未显式报错,但连接在
onclose时返回 code=1006(abnormal closure) - 网络抖动一次,整个文件重传,无法和 UI 进度条、暂停按钮联动
分片发送必须带 offset 元数据,不能只靠 chunkIndex
只传 chunkIndex: 5 是危险的:后端无法判断这第 5 片是否对应文件第 5MB,也无法处理乱序或重复帧。真正可靠的是用字节偏移量(offset)作为每片的唯一坐标。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 前端读取:用
file.slice(offset, offset + chunkSize)或FileReader.readAsArrayBuffer()精确截取 - 数据包结构建议为:
Uint32Array.BYTES_PER_ELEMENT字节的 offset(小端) + 原始二进制数据 - 后端解析:Node.js 用
buf.readInt32LE(0),Go 用binary.Read(buf, binary.LittleEndian, &offset) - 前端持久化 offset:必须存在
localStorage或 IndexedDB,不能只放内存变量里
重连后怎么安全续传,避免错位写和重复写
前端记录的 offset 不可信——网络发出但服务端没收到,前端却已自增,下次重连就会跳过一段,导致文件空洞;或者服务端收到了但响应丢了,前端重发又造成覆盖。
- 重连成功后,先发一个 HTTP 查询请求(如
GET /upload/status?fileId=abc)或 WebSocket ping 帧附带fileId,拉取服务端当前已接收的最大offset - 服务端写入每个分片后,必须执行
fseek(fd, offset, SEEK_SET); fwrite(...); fsync(fd);,并返回该分片的哈希(如xxhash32(chunkData)) - 前端收到响应后,用相同算法算本地 chunk 哈希,比对一致才更新本地
offset记录 - 若服务端返回的
maxOffset> 本地记录值 → 说明丢包,从该位置重发;若→ 本地状态错乱,应以服务端为准重置
合并阶段必须校验整体哈希,不能只数分片个数
很多实现只检查 receivedChunks.length === totalChunks 就认为上传完成,这是严重漏洞。分片可能被篡改、错位写入、或最后几片因网络中断未落盘,但文件句柄已关闭,最终得到一个大小正确但内容损坏的文件。
合并完成后,务必用服务端计算的全文件哈希(如 SHA-256)与客户端重新计算的哈希比对;同时校验 fs.statSync(filePath).size === expectedSize。这两个校验缺一不可——前者防内容篡改,后者防末尾截断。










