协同编辑的核心是ot或crdt算法,而非websocket本身;websocket仅负责低延迟传输操作,ot引擎通过变换并发操作确保最终一致性。

协同编辑不是靠“发消息+刷新”实现的,WebSocket 本身不解决冲突,它只负责把操作可靠、低延迟地传过去。真正让多人编辑不乱套的,是服务端的 OT 或 CRDT 引擎 —— 这一点绝大多数教程开头就埋错坑。
为什么直接广播字符串或 HTML 会出大问题
很多初学者一上来就 conn.send(document.innerHTML) 或监听 input 事件后发整个内容快照。这会导致:
- 网络带宽浪费严重,一个 10KB 文档每次输入都传全量
- 无法处理并发:用户 A 删第 5 行,用户 B 同时改第 3 行,谁先到谁赢,后到的直接覆盖前者的意图
- 光标、选区、滚动位置全部丢失,协作体验断层
- 移动端频繁重绘卡顿,尤其在富文本中
正确做法是只传输「操作」(Operation):比如 { type: "insert", position: 12, text: "hello" } 或 { type: "delete", position: 45, length: 3 },再由所有客户端用相同逻辑应用该操作。
Java + WebSocket 必须补上的 OT 核心逻辑
Spring Boot 项目里光有 @ServerEndpoint 和 session.getBasicRemote().sendText() 远远不够。你得在消息到达后、广播前插入 OT 变换步骤:
- 每个操作必须携带
clientId和version(即“我基于哪个版本做的这个修改”) - 服务端维护一个全局
documentVersion和操作历史队列 - 收到新操作
op时,遍历已确认的操作列表,对op依次调用transform(op, existingOp) - 变换后的
op'才写入历史、更新documentVersion,并广播给其他客户端
示例关键判断逻辑:if (existingOp.type == INSERT && op.position >= existingOp.position) { op.position += existingOp.text.length; } —— 这类规则必须覆盖所有操作组合(insert/delete/format),漏一条就会导致最终状态不一致。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
前端编辑器怎么接 WebSocket 操作流
别自己从零解析光标位置或 DOM diff。优先用支持协作抽象层的编辑器:
-
tui.editor:暴露change和caretChange事件,可直接映射为insert/delete操作,配合其setMarkdown()安全回放 -
CodeMirror 6:用Transaction对象天然对应操作概念,dispatch()后取changes字段生成操作 - 避免用
contenteditable+innerHTML,它没有操作溯源能力,也无法精确计算位置偏移
客户端收到服务端推送的 Operation 后,必须用和发送端**完全相同的 apply 函数**执行,否则 OT 链断裂。建议把核心操作应用逻辑抽成独立 JS/TS 模块,前后端共用(或 Java 与 JS 严格对齐算法)。
连接断开时状态怎么不丢、不乱
WebSocket 断连不是异常,是常态。但很多人忽略两点:
- 客户端重连后,不能直接发当前文档内容 —— 此时服务端已有新版本,必须先拉取最新
version和缺失的操作日志(/api/sync?fromVersion=123) - 服务端不能仅靠内存存操作历史;必须落库(如 MySQL 的
operation_log表),字段至少含doc_id、client_id、version、operation_json、created_at - 前端本地需缓存未确认的操作(
pendingOps),重连成功后再按序重发,并等待服务端返回ack(version)才清空
最易被忽略的点:服务端广播操作时,**必须跳过触发该操作的原始客户端**,否则它会把自己刚发的操作又 apply 一遍,造成重复插入或双倍删除。










