核心是扛住并发和断线重连:需用gorilla/websocket而非标准库,读写需隔离goroutine并加锁,消息去重靠seq+msg_id,历史消息用Sorted Set存储,真实网络下需实测调优。

Go 语言实现游戏聊天系统,核心不在“怎么写”,而在“怎么扛住并发和断线重连”——用 net/http 或 gorilla/websocket 搭个回显聊天不难,但玩家打团时消息堆积、弱网下频繁重连、历史消息同步错乱,这些才是真卡点。
为什么不用标准库 net/http 直接升级 WebSocket?
标准库没提供 WebSocket 升级封装,http.ResponseWriter 和 http.Request 本身不支持直接转成 WebSocket 连接。硬写要手动解析 Upgrade 头、校验 Sec-WebSocket-Key、拼接响应头,极易出错且不兼容主流客户端(比如 Unity 的 WebSocketSharp 或微信小游戏 SDK)。
实操建议:
- 直接用
gorilla/websocket—— 它封装了握手、ping/pong、消息分帧、连接状态管理,是 Go 生态事实标准 - 别自己实现
Upgrader.CheckOrigin放开跨域就完事;生产环境必须校验r.Header.Get("Origin")或透传鉴权 token(比如从 query string 提取token=xxx) - 升级 handler 里别做耗时操作(如查 DB),先 accept 连接,再异步加载用户信息,否则会阻塞握手,导致客户端报
WebSocket connection failed: Error during WebSocket handshake
websocket.Conn 的读写必须配对加锁吗?
不是“必须”,而是“不加锁必崩”。websocket.Conn 的 WriteMessage 和 ReadMessage 方法**不是并发安全的**:多个 goroutine 同时调用 WriteMessage 可能导致帧错乱,客户端收到粘包或解析失败;同时读写则可能触发 panic: concurrent write to websocket connection。
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
常见错误现象:
- 玩家 A 发一条“来了”,B 看到的是“来了[object Object]”或乱码
- 后台日志反复出现
write tcp xxx: use of closed network connection,其实是写冲突导致连接被底层关闭
实操建议:
- 每个连接独占一个 goroutine 负责
ReadMessage(处理输入),再起一个 goroutine 用for-select循环监听该连接专属的chan []byte做WriteMessage(输出) - 别用
conn.SetWriteDeadline配合time.AfterFunc做心跳——它只影响下一次写,得在每次WriteMessage前重设 deadline - 如果要用广播(比如全服公告),别遍历所有
*websocket.Conn并发写,改用“连接池 + 写队列”,每连接一个 writer goroutine 自行消费队列
断线后怎么保证消息不丢、重连不重复?
WebSocket 断开瞬间,客户端可能已发出消息但服务端还没收到;重连成功后,客户端常会重发未 ACK 的消息,而服务端若无去重逻辑,就会让“撤退!”发三遍。
关键不在协议层(WS 本身无 ACK),而在业务层设计:
- 客户端发每条消息带单调递增的
seq(int64)和timestamp,服务端按(user_id, seq)做内存去重(用map[[2]interface{}]struct{}或更稳妥的 LRU cache) - 服务端给每条下发消息附带
msg_id(如fmt.Sprintf("%d-%d", time.Now().UnixMilli(), rand.Intn(1000))),客户端收到后存本地Set,重连后带上最近 100 个msg_id请求“补漏” - 别把历史消息全塞进 Redis List 然后
LTRIM——高并发写入时 List 长度波动大,LRANGE分页易漏;改用 Sorted Set,score 用时间戳,用ZRANGEBYSCORE精确拉取
真正难的从来不是“连上”或“发出去”,而是玩家在 4G 切 WiFi 的 800ms 里,那条“BOSS 二阶段了”有没有被准确送达、不重复、不延迟超过 1.5 秒——这要求你对 gorilla/websocket 的 WriteBufferPool、心跳超时阈值、连接池驱逐策略,都得亲手测过真实设备网络抖动数据,而不是只跑通 localhost。


















