Einführung: Über einfaches WebSocket in Go hinaus
Grundlegende WebSocket-Beispiele in Go zeigen normalerweise eine einfache Echo-Verbindung. In echten Produktionsprojekten — von Trading-Terminals über Multiplayer-Spiele bis hin zu Benachrichtigungssystemen — wird viel mehr benötigt: Raumverwaltung, Graceful Shutdown, Skalierung und ordnungsgemäße Fehlerbehandlung. In diesem Artikel analysieren wir ein fortgeschrittenes Muster für die Erstellung von Echtzeitanwendungen in Go unter Verwendung der gorilla/websocket-Bibliothek.
Wir erstellen eine Architektur basierend auf Hub (zentraler Verteiler) und Client (einzelne Verbindung), fügen Raumunterstützung, einen Fehlertoleranzmechanismus hinzu und zeigen, wie man den Server ordnungsgemäß herunterfährt.
Hub- und Client-Architektur: Die Grundlage eines Echtzeit-Servers
Warum Hub?
Jeder WebSocket-Client ist eine separate Goroutine. Sie direkt von einem HTTP-Handler aus zu verwalten, ist gefährlich: Es führt zu Datenkonflikten und Speicherlecks. Das Hub-Muster löst dieses Problem, indem es als einzelner Kanal für Registrierung, Entfernung und Nachrichtenverteilung fungiert.
Hub-Implementierung
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("Client verbunden. Gesamt: %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 { // Nur an den Zielraum senden if client.room == message.Room || message.Room == "" { select { case client.send <- message.Data: default: // Wenn der Kanal voll ist — Client trennen h.mu.RUnlock() h.unregister <- client h.mu.RLock() } } } h.mu.RUnlock() } }}Schlüsselpunkte:
- Kanäle — register, unregister, broadcast — synchronisieren Goroutinen sicher.
- RWMutex — erlaubt gleichzeitiges Lesen der Client-Liste, blockiert aber Schreibvorgänge.
- Raumbasierte Weiterleitung — eine Nachricht wird nur an Clients im selben Raum gesendet.
Raum- und Abonnementverwaltung
Räume sind logische Gruppen von Clients. Zum Beispiel hat in einem Chat jede Unterhaltung ihren eigenen Raum. In unserem Hub wird der Raum durch das room-Feld in Client festgelegt. Sehen wir uns an, wie ein Client einen Raum abonniert.
HTTP-Handler mit 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 // Für Produktion durch Ursprungsprüfung ersetzen },}
func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err !=