WebSocket连上即断主因是握手失败,需检查服务端是否被中间件拦截Upgrade请求、响应是否“干净”(无额外Header/body)、gorilla/websocket的CheckOrigin和Subprotocols配置是否正确,并确保Upgrade由handler直接处理。

WebSocket连接建立后立刻断开,net/http 服务端没报错但客户端收不到 101 Switching Protocols
常见原因是 HTTP 中间件(如 CORS、gzip、日志中间件)在 Upgrade 请求上做了拦截或响应体写入。WebSocket 升级必须是“干净”的响应——不能有额外的 Header 冲突(比如 Content-Length)、不能提前写 body、不能被中间件重写状态码。
- 确保使用
http.ServeMux或直接http.ListenAndServe,避免用封装过深的框架(如 Gin 默认启用 gzip 和 CORS,需手动禁用 Upgrade 路径) - Upgrade 请求必须由 handler 直接处理:检查
r.Header.Get("Upgrade") == "websocket",且调用upgrader.Upgrade(w, r, nil)前不能有任何w.Write()或log.Print() - Go 标准库
gorilla/websocket的Upgrader.CheckOrigin默认拒绝非同源请求,开发时设为func(r *http.Request) bool { return true },上线务必改回严格校验
gorilla/websocket 的 WriteMessage 阻塞,多人协作白板卡顿
根本原因不是 WebSocket 本身慢,而是多个 goroutine 并发调用同一个 *websocket.Conn 的写方法导致竞争,底层 writeLock 串行化所有写操作,消息堆积后延迟飙升。
- 每个连接必须配一个专属的写 goroutine:启动时起一个 loop,从 channel 读消息并调用
conn.WriteMessage(),其他逻辑只往该 channel 发送 - 避免在 handler 里直接
conn.WriteMessage()—— 尤其是广播场景,会把所有连接的写操作压进一个线程 - 白板数据建议用
websocket.BinaryMessage发送 protobuf 或 msgpack 序列化后的字节流,比 JSON 文本小 40%~60%,减少 writeLock 持有时间
白板同步出现“笔迹跳跃”或“操作丢失”,conn.ReadMessage() 没做消息顺序控制
WebSocket 保证帧顺序,但应用层若不控制指令执行时机,就会因网络抖动、多客户端并发提交导致状态不一致。例如 A 画线段 L1,B 同时删图层,若服务端按接收顺序处理,可能删掉还没存进内存的 L1。
- 给每条白板操作加单调递增的
seq字段(服务端生成或客户端带 NTP 时间戳),服务端按seq排序后再应用到共享状态树 - 客户端发送前缓存未确认的操作,收到服务端
{"ack": seq}再清除;超时未 ack 则重发(注意幂等:操作 ID + seq 组成唯一键) - 避免用
map[string]interface{}解析前端 JSON —— 字段名拼错或类型错会导致静默丢数据,改用 struct +json.Unmarshal并检查err
部署后 Safari 连不上,Chrome 正常,Sec-WebSocket-Protocol 头缺失或不匹配
Safari 对 WebSocket 子协议校验更严格。如果客户端指定了 protocols: ["whiteboard-v1"],而 Go 服务端 upgrader.Subprotocols 没声明或顺序不对,Safari 会直接关闭连接且不触发 onerror。
立即学习“go语言免费学习笔记(深入)”;
- 服务端初始化
upgrader时必须显式设置Subprotocols: []string{"whiteboard-v1"} - 客户端
new WebSocket(url, ["whiteboard-v1"])的协议数组必须和服务端声明的完全一致(大小写、顺序) - 用浏览器 devtools 的 Network → WS → Headers 查看 Request Headers 里的
Sec-WebSocket-Protocol值,和服务端日志对比是否匹配


















