WebSocket:gorilla/websocket 升级、消息广播与心跳(基础版)
WebSocket 在一条 TCP 连接上做全双工通信,适合聊天、推送、协同编辑。Gin 本身不处理升级,社区最常用 gorilla/websocket 完成升级与帧读写,API 以官方仓库 README 与 pkg.go.dev 为准。
1. 升级连接
路由里用 Upgrader 把普通 HTTP 请求升级为长连接:
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true // 生产按来源白名单收紧,不要一律放行
},
}
func WsHandler(c *gin.Context) {
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
return // 升级失败,由库处理了响应
}
defer conn.Close()
// ... 进入读写循环
}
升级成功后 HTTP 语义不再适用,业务数据走连接帧。在 Gin 里挂载并先鉴权再升级:r.GET("/ws", AuthRequired(), WsHandler),避免匿名连接占资源。
2. 读写循环与广播
经典模型是 Hub:连接注册进 map,收到的消息统一转发:
type Hub struct {
mu sync.Mutex
clients map[*websocket.Conn]bool
}
func (h *Hub) broadcast(msg []byte) {
h.mu.Lock()
defer h.mu.Unlock()
for conn := range h.clients {
conn.WriteMessage(websocket.TextMessage, msg)
}
}
func readLoop(h *Hub, conn *websocket.Conn) {
defer conn.Close()
for {
_, data, err := conn.ReadMessage()
if err != nil {
h.mu.Lock()
delete(h.clients, conn)
h.mu.Unlock()
return
}
h.broadcast(data) // 示例语义:原样转给所有人
}
}
3. 心跳:探测死连接
WebSocket 没有自动超时断连,要靠应用层心跳:服务端定期发 Ping,客户端回 Pong 就续命;读超时一到说明对端失联,ReadMessage 返回错误并回收连接:
conn.SetReadLimit(1024) // 防超大消息
conn.SetReadDeadline(time.Now().Add(60 * time.Second))
conn.SetPongHandler(func(string) error { // 收到 Pong 重置读超时
return conn.SetReadDeadline(time.Now().Add(60 * time.Second))
})
go func() { // 示例:每 30 秒发一次 Ping
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for range ticker.C {
if err := conn.WriteControl(websocket.PingMessage,
nil, time.Now().Add(10*time.Second)); err != nil {
conn.Close()
return
}
}
}()
注意点
- 建议一个连接同时只有一个 goroutine 在写;
WriteMessage并发调用不安全,广播侧要加锁或收敛到专用写协程。 - 帧大小默认无上限,务必
SetReadLimit。 - 优雅关闭:广播 CloseMessage 并等读循环退出,别直接强杀连接。
- 协议细节以 IETF RFC 6455 与 gorilla/websocket 官方文档为准。
小结
升级(Upgrader)→ 读写循环 → 广播 Hub → 心跳保活,是 WebSocket 服务的基本骨架。先想清楚「谁发给谁」再选 Hub/房间模型;更完整的例子可参考 gorilla 官方 chat 示例。