WebSocket本身不支持断点续传,需前后端协同实现:前端用offset+chunkSize分片并持久化状态,后端幂等写入+哈希校验,重连时通过查询服务端最大offset同步位置,避免错位或重复写。

WebSocket 本身不支持断点续传,所谓“断点续传”是前端状态管理 + 后端分片校验共同实现的协作逻辑,不是协议能力。
为什么 WebSocket.send() 传大文件会失败或卡死
直接调用 send(file) 或 send(file_get_contents()) 几乎必然出问题:
- 浏览器或服务端对单帧(frame)有硬限制:Tomcat 默认 8KB,iOS SRWebSocket 会把整块
ArrayBuffer加载进内存,200MB 文件极易触发 JetsamEvent 杀进程 - 超限帧被静默丢弃,连接可能断开但无明确错误码(如
Connection reset by peer) - 网络中断后,
WebSocket不记录“已发到第几个字节”,前端无法知道从哪 resume - PHP 客户端库(如 textalk/websocket)普遍全量读入内存再打包,
memory_limit很快耗尽
必须用 offset + chunkSize 标识每一片
只传 chunkIndex(如 0、1、2…)不可靠——乱序、丢包、重连后无法对齐。用字节偏移量才是唯一稳态方案:
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 前端每次读取
file.slice(offset, offset + chunkSize),确保精确切片 - 每个 WebSocket 数据包结构建议为:
4 字节 offset(小端) + 原始二进制数据,后端用Buffer.readInt32LE(0)解析 - 服务端收到后直接
fseek(fd, offset, SEEK_SET); fwrite(...),无需维护分片顺序状态 - 前端必须把当前
offset持久化到IndexedDB或localStorage(Web),不能只存在内存里
重连后怎么安全续传,避免错位写、重复写
这是最容易损坏文件的地方。仅靠前端记录 offset 是危险的——网络抖动可能导致包发出但服务端未落盘,前端却自以为成功了。
- 重连成功后,前端先发一条查询消息(如
{"action":"query_offset","fileId":"xxx"}),服务端返回当前已接收的最大offset - 若服务端返回值 > 前端本地记录值 → 说明有分片丢失,前端从该
offset开始重发 - 若服务端返回值
- 服务端每次写入后立即返回该分片的哈希(如
xxhash32),前端比对一致才更新本地offset - 最终合并前,必须校验总文件大小 + 整体哈希(如 SHA-256),不能只数分片个数
别让 PHP 当大文件上传通道
PHP 原生不支持 WebSocket 客户端流式二进制发送,主流库会把整个文件读进内存,这不是配置能绕过的缺陷。
- 硬要用 PHP 发送?降级走 HTTP 分块上传:
curl_setopt($ch, CURLOPT_INFILE, $fp)+CURLOPT_READFUNCTION,内存可控 - 更推荐做法:PHP 只做信令中转——提供
/api/upload/init返回{ws_url: "...", upload_id: "xxx"},由前端直连 WebSocket 服务 - 上传进度轮询走 HTTP(
/api/upload/status?upload_id=xxx),别塞进 WebSocket;状态清理需设 TTL(如 30 分钟) - 如果服务端是多实例,WebSocket 绑定关系(upload_id ↔ client)必须存 Redis,不能放进程内存
真正难的不是切片和发包,而是前后端对“这一片到底算不算成功”的原子共识——它藏在 offset 同步时机、哈希比对位置、fsync 调用顺序这些细节里。漏掉任意一环,断点续传就变成“断点错传”。

















