Introdução: além do WebSocket simples em Go
Exemplos básicos de WebSocket em Go geralmente mostram uma simples conexão de eco. Em projetos reais de produção — desde terminais de bolsa de valores até jogos multiplayer e sistemas de alerta — é necessário muito mais: gerenciamento de salas, Graceful Shutdown, escalabilidade e tratamento correto de erros. Neste artigo, vamos analisar um padrão avançado para construir aplicações em tempo real em Go usando a biblioteca gorilla/websocket.
Vamos criar uma arquitetura baseada em Hub (central dispatcher) e Client (conexão individual), adicionar suporte a salas, um mecanismo de tolerância a falhas e mostrar como finalizar corretamente a execução do servidor.
Arquitetura Hub e Client: a base do servidor em tempo real
Por que Hub?
Cada cliente WebSocket é uma goroutine separada. Gerenciá-los diretamente a partir de um handler HTTP é perigoso: isso leva a condições de corrida e vazamentos de memória. O padrão Hub resolve o problema atuando como um canal único para registro, remoção e distribuição de mensagens.
Implementação do Hub
package main
import ( "log" "sync")
type Client struct { hub *Hub send chan []byte room string}
type Hub struct { clients map[*Client]bool broadcast chan Message register chan *Client unregister chan *Client mu sync.RWMutex}
type Message struct { Room string Data []byte Sender *Client}
func NewHub() *Hub { return &Hub{ clients: make(map[*Client]bool), broadcast: make(chan Message, 256), register: make(chan *Client), unregister: make(chan *Client), }}
func (h *Hub) Run() { for { select { case client := <-h.register: h.mu.Lock() h.clients[client] = true h.mu.Unlock() log.Printf("Cliente conectado. Total: %d", len(h.clients))
case client := <-h.unregister: h.mu.Lock() if _, ok := h.clients[client]; ok { delete(h.clients, client) close(client.send) } h.mu.Unlock()
case message := <-h.broadcast: h.mu.RLock() for client := range h.clients { // Envia apenas para a sala correta if client.room == message.Room || message.Room == "" { select { case client.send <- message.Data: default: // Se o canal estiver cheio — desconecta o cliente h.mu.RUnlock() h.unregister <- client h.mu.RLock() } } } h.mu.RUnlock() } }}Pontos-chave:
- Canais — register, unregister, broadcast — sincronizam as goroutines com segurança.
- RWMutex — permite ler a lista de clientes simultaneamente, mas bloqueia a escrita.
- Roteamento baseado em sala — a mensagem é enviada apenas para clientes na mesma sala.
Gerenciamento de salas e inscrições
Salas (Rooms) são grupos lógicos de clientes. Por exemplo, em um chat, cada conversa tem sua própria sala. Em nosso Hub, a sala é definida pelo campo room do Client. Vamos ver como um cliente se inscreve em uma sala.
Handler HTTP com WebSocket Upgrade
import ( "net/http" "github.com/gorilla/websocket")
var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, CheckOrigin: func(r *http.Request) bool { return true // Para produção, substituir pela verificação de origin },}
func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err !=
```