必须应用层分片上传大文件,因WebSocket底层隐式分片不可控,易丢帧错乱、内存溢出、无法断点续传;需Blob.slice+FileReader流式读取、带offset元数据发送、服务端按偏移写入并支持状态查询。

WebSocket本身不支持直接发送超大文件,浏览器会在底层自动分片,但不可控;真要可靠上传大文件,必须自己实现应用层分片——这不是可选项,是必选项。
为什么不能直接 ws.send(file)?
现代浏览器对单次 send() 的 ArrayBuffer 大小没有硬性限制,但实际会触发隐式分片:Chrome 通常在 >16MB 时拆帧,Firefox 行为略有不同。问题在于:
- 服务端若未按 WebSocket 协议解析
FIN和opcode(尤其是中间帧opcode=0),会直接丢弃或错乱重组 - 网络丢一帧 → 整条消息失效,浏览器不会重传,应用层无感知
- 内存风险:
file.arrayBuffer()加载整文件进内存,2GB 文件直接 OOM - 无法断点、无法校验、无法并发、无法显示真实进度
前端分片核心:用 Blob.slice() + FileReader 流式读取
不要一次性把整个文件转成 ArrayBuffer。正确做法是按块读取,边读边发:
- 固定分片大小(如
chunkSize = 1024 * 1024即 1MB),避免最后片过小导致逻辑分支增多 - 用
file.slice(start, end)获取Blob片段,再用FileReader.readAsArrayBuffer()转换,不阻塞主线程 - 每次只持有一个分片的
ArrayBuffer,内存占用恒定 - 配合
Promise链或async/await控制串行发送,避免服务端写乱序
示例关键片段:
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
function uploadChunk(ws, blob, offset) {
const reader = new FileReader();
return new Promise((resolve, reject) => {
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsArrayBuffer(blob);
}).then(buffer => {
// 可选:封装元数据,如 { offset, size, total, md5 }
const packet = new Uint8Array(buffer.byteLength + 8);
new DataView(packet.buffer).setUint32(0, offset, true); // 小端偏移量
packet.set(new Uint8Array(buffer), 8);
ws.send(packet);
});
}服务端必须按偏移写入,而非追加
客户端发的每片都带 offset,服务端绝不能简单 fs.appendFile。否则网络重传、乱序到达会导致文件损坏:
- 接收后先校验
offset是否与预期一致(可维护一个nextExpectedOffset) - 用
fs.write(fd, buffer, 0, buffer.length, offset)直接写入指定位置 - 收到全部分片后,再
fs.ftruncate(fd, finalSize)截断冗余空间 - 推荐用临时文件名(如
upload_abc123.tmp),全部写完再rename成目标名,避免脏文件暴露
断点续传依赖服务端状态查询接口
WebSocket 是无状态长连接,断连后客户端不知道已传到哪。必须额外提供 HTTP 接口供查询:
- 上传前先 POST
/api/upload/init,获取fileId和已上传分片列表 - 客户端对比本地分片总数和已传列表,跳过已成功的分片
- 服务端存储使用轻量方案即可:Redis Hash(
HSET upload:abc123 chunk_0 1)或 SQLite - 注意清理机制:72 小时未完成的临时文件和记录应自动 GC
真正难的不是切片,而是让每一片都“可定位、可验证、可重入”。偏移量字段、服务端随机 fileId、分片级哈希校验(如每片附带 CRC32),这些细节漏掉一个,上线后就会出静默数据损坏。

















