You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go WebSocket如何实现多客户端计时器同步

问题根因

现有实现用单个全局变量wsConn存储WebSocket连接,新客户端建立连接时会直接覆盖该变量,后续计时器触发时只会向最新连接推送消息,旧连接因收不到数据停止更新,本质是未做多连接生命周期管理、未将计时器状态与单连接逻辑解耦。

改造方案

服务端(Go)改造

核心思路是全局维护唯一计时器状态 + 线程安全的多连接池 + 全量广播机制,具体步骤如下:

  • 替换单连接全局变量,定义线程安全的共享状态
    废弃原有的单wsConn全局变量,用带锁的结构存储连接池和全局统一的计时器状态,避免多goroutine竞态:
    import (
        "sync"
        "time"
        "github.com/gorilla/websocket"
        "github.com/gorilla/mux"
    )
    
    var (
        // 活跃客户端连接池,key为唯一连接ID,value为WebSocket连接实例
        clientPool = make(map[string]*websocket.Conn)
        poolMu     sync.RWMutex
    
        // 全局唯一计时器状态,所有客户端共享
        globalTimer struct {
            mu         sync.Mutex
            TotalSec   int           // 倒计时总秒数
            RemainSec  int           // 剩余秒数
            IsRunning  bool          // 运行状态标记
            Ticker     *time.Ticker  // 全局唯一定时器实例
            StartAt    time.Time     // 计时器启动时间,用于校准减少误差
        }
    )
    
  • 改造/socket端点的连接处理逻辑
    每个新连接建立时独立处理读逻辑,不阻塞其他连接:
    1. 连接升级完成后,生成唯一连接ID,加锁将新连接存入连接池
    2. 若此时全局计时器已在运行,立即向新连接推送当前剩余秒数,让新客户端直接对齐现有计时状态,无需等待下一秒广播
    3. 为每个连接单独启动读协程,接收客户端传来的启动/重置倒计时请求
    4. 监听连接断开事件(读消息返回错误时),加锁将失效连接从连接池移除,避免向无效连接写数据引发panic
  • 抽离全局唯一计时器调度逻辑,禁止每个连接单独创建Ticker
    1. 收到任意客户端发来的带StartTime(初始倒计时秒数)的请求时,加锁重置全局计时器状态:更新总秒数、剩余秒数、记录启动时间,若已有Ticker在运行则先停止旧Ticker,再启动新的1秒周期Ticker
    2. Ticker每次触发时,不依赖纯计数更新剩余时间,通过time.Since(globalTimer.StartAt)计算已流逝时间,得出准确剩余秒数,避免Ticker累积误差
    3. 剩余秒数更新后,遍历整个连接池,将最新剩余时间广播给所有活跃连接;若剩余秒数归0,停止Ticker、标记运行状态为结束,广播倒计时结束消息
    4. 广播时如果遇到写失败的连接,直接异步从连接池移除,不要因为单个连接故障中断全量广播流程

核心广播逻辑参考实现:

func broadcastLatestState() {
    poolMu.RLock()
    defer poolMu.RUnlock()
    msg := map[string]any{
        "type":      "tick",
        "remainSec": globalTimer.RemainSec,
    }
    if globalTimer.RemainSec <= 0 {
        msg["type"] = "finished"
    }
    for connID, conn := range clientPool {
        if err := conn.WriteJSON(msg); err != nil {
            // 异步清理失效连接,不阻塞当前广播
            go func(id string) {
                poolMu.Lock()
                defer poolMu.Unlock()
                delete(clientPool, id)
            }(connID)
        }
    }
}

注意:所有对连接池、全局计时器状态的读写操作必须加对应锁,否则高并发场景下会出现数据竞态,甚至触发panic。

前端(Vue)适配

原有逻辑基本可复用,仅需补充两点:

  • WebSocket连接建立后,不需要等待用户输入,直接接收服务端主动推送的初始状态(新连接接入时的对齐消息),直接渲染剩余时间即可
  • 消息处理逻辑兼容两种消息类型:普通tick消息更新剩余秒数并格式化为时分秒展示,收到finished消息时弹出归零提示即可

内容的提问来源于stack exchange,提问作者Áron Pop Adorján

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 15:00:45