WebSocket仅是传输管道,冲突解决依赖OT或CRDT算法:OT通过操作变换适配并发编辑,CRDT基于因果关系实现离线自动收敛;二者均需结构化操作、版本控制与有序广播。

WebSocket 本身不解决冲突,它只是把操作从客户端传到服务端、再广播出去的管道。真正让多人编辑不乱套的,是上层选对并用好一致性算法——目前主流就两种:OT 和 CRDT。
OT(操作变换)适合文本类强顺序场景
它把每次插入、删除都变成带位置和内容的结构化操作,关键在“变换”:当用户 A 和 B 同时在第 5 位操作,后发出的操作会自动调整位置,避免错位覆盖。
- 每条操作必须含 type(insert/delete)、index(基于当前状态的 UTF-16 位置)、text 或 length、clientId、docId 和 baseVersion(文档版本号)
- 服务端按 serverSeq 全局排序,广播前不做变换;客户端收到他人操作后,先对本地待发操作做
transform(),再发出 - 开源实现可参考 ShareDB、ot-json0,前端配合 CodeMirror 6 的 collab 插件或 Monaco 的底层 edit API
CRDT 适合离线强一致与弱网环境
它把文档建模成带唯一 ID 和因果关系的原子操作集合,各端独立应用变更,网络恢复后自动收敛。不需要服务端排序,天然支持 P2P 或 WebRTC 直连。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 典型库如 Yjs 中的 Y.Text,操作自带逻辑时间戳和依赖链(
deps),客户端只需追加、合并、重放 - 消息体仍需含 docId、clientId、op(如 {type:"insert", id:"a1b2", left:"c3d4", right:"e5f6", text:"x"})和 timestamp
- 内存占用略高,但调试友好——所有操作可序列化、可回放,适合需要审计或回溯的协作场景
消息结构和传输必须支撑算法落地
再好的算法,如果消息乱序、丢包、没标识,也白搭。WebSocket 层要做的不是“转发”,而是提供可排序、可追溯、可补全的基础。
- 每条消息带 timestamp(毫秒级)或 sequenceId,客户端收到后进缓冲队列,按序消费,丢弃明显滞后的旧包
- 新用户连接时,服务端主动推送最近 15 秒操作日志(标记
isHistorical: true),客户端跳过动画、直接绘制 - 断线重连不能只补最后一条,要拉取“快照 + 增量日志”,确保重放路径与在线时一致
前端渲染必须和协作状态解耦
编辑器(如 Quill、Monaco)只是视图,不该直接处理同步逻辑。所有变更应由协作引擎(如 Y.Doc 或 ShareDB 的 Doc)统一调度。
- 用户输入触发的操作先存入 pendingOps 队列,带本地时间戳和随机 ID;断连时不丢,重连后提交合并
- 光标和选区需单独同步,消息中注明 clientId 和 range,避免远程操作意外覆盖本地焦点
- 禁用
editor.setValue()这类全量覆盖,改用支持原子操作的 API,比如view.dispatch()或executeEdits()

















