Erweiterte Verwendung von Namespaces¶
Namespaces sind eine leistungsstarke Funktion von Socket.IO, die es ermöglicht, mehrere Kommunikationskanäle über eine einzige WebSocket-Verbindung zu multiplexen.
Inhaltsverzeichnis¶
Was sind Namespaces?¶
Namespaces sind logisch getrennte Kommunikationskanäle, die sich dieselbe physische WebSocket-Verbindung teilen. Jeder Namespace:
- Hat seinen eigenen Satz von Events
- Verwaltet seine eigene Verbindung/Trennung
- Kann unterschiedliche Handler haben
- Teilt sich die zugrundeliegende TCP/WebSocket-Verbindung
Standard-Namespace¶
Der Standard-Namespace ist /. Alle Events ohne spezifischen Namespace gehen hierhin:
// Diese drei Codes sind äquivalent
client.On("event", handler)
client.Of("/").On("event", handler)
defaultNs := client.Of("/")
defaultNs.On("event", handler)
Anwendungsfälle¶
1. Trennung nach Funktionalität¶
Trennen Sie verschiedene Bereiche Ihrer Anwendung in unterschiedliche Namespaces:
// Öffentlicher Chat
publicChat := client.Of("/chat")
publicChat.On("message", handlePublicMessage)
// Systembenachrichtigungen
notifications := client.Of("/notifications")
notifications.On("alert", handleAlert)
// Echtzeit-Daten-API
dataStream := client.Of("/stream")
dataStream.On("update", handleDataUpdate)
2. Trennung nach Zugriffsebene¶
Verwenden Sie Namespaces für unterschiedliche Berechtigungsebenen:
// Öffentlicher Namespace (alle können zugreifen)
public := client.Of("/public")
public.On("announcement", handleAnnouncement)
// Namespace für authentifizierte Benutzer
authenticated := client.Of("/authenticated")
authenticated.OnConnect(func() {
authenticated.Emit("auth", map[string]interface{}{
"token": userToken,
})
})
authenticated.On("private-data", handlePrivateData)
// Administrator-Namespace
admin := client.Of("/admin")
admin.OnConnect(func() {
admin.Emit("auth", map[string]interface{}{
"token": adminToken,
"role": "admin",
})
})
admin.On("admin-event", handleAdminEvent)
3. Virtuelle Räume¶
Verwenden Sie Namespaces als separate virtuelle Räume:
// Verschiedene Chat-Räume
lobby := client.Of("/chat/lobby")
lobby.On("message", handleLobbyMessage)
gameRoom := client.Of("/chat/game-room-1")
gameRoom.On("message", handleGameRoomMessage)
privateRoom := client.Of("/chat/private-123")
privateRoom.On("message", handlePrivateMessage)
4. Microservices¶
Jeder Microservice kann seinen eigenen Namespace haben:
// Benutzer-Service
users := client.Of("/services/users")
users.On("user-update", handleUserUpdate)
// Zahlungs-Service
payments := client.Of("/services/payments")
payments.On("payment-status", handlePaymentStatus)
// Benachrichtigungs-Service
notifications := client.Of("/services/notifications")
notifications.On("notify", handleNotification)
Architektur¶
Unabhängiger Lebenszyklus¶
Jeder Namespace hat seinen eigenen Lebenszyklus:
chat := client.Of("/chat")
api := client.Of("/api")
chat.OnConnect(func() {
fmt.Println("Chat verbunden")
chat.Emit("join", "general")
})
chat.OnDisconnect(func(reason string) {
fmt.Println("Chat getrennt:", reason)
})
api.OnConnect(func() {
fmt.Println("API verbunden")
api.Emit("subscribe", []string{"users", "posts"})
})
api.OnDisconnect(func(reason string) {
fmt.Println("API getrennt:", reason)
})
Isolierte Events¶
Events in einem Namespace beeinflussen andere Namespaces nicht:
chat := client.Of("/chat")
api := client.Of("/api")
// Dieser Handler empfängt nur Events von /chat
chat.On("message", func(data ...interface{}) {
fmt.Println("Chat-Nachricht:", data[0])
})
// Dieser Handler empfängt nur Events von /api
api.On("message", func(data ...interface{}) {
fmt.Println("API-Nachricht:", data[0])
})
// Emission in /chat beeinflusst /api nicht
chat.Emit("message", "Hallo vom Chat")
api.Emit("message", "Hallo von der API")
Implementierungsmuster¶
Muster: Namespace-Manager¶
Verwalten Sie mehrere Namespaces zentral:
type NamespaceManager struct {
client *socketio.Socket
namespaces map[string]*socketio.Namespace
}
func NewNamespaceManager(url string) *NamespaceManager {
return &NamespaceManager{
client: socketio.New(url),
namespaces: make(map[string]*socketio.Namespace),
}
}
func (m *NamespaceManager) Get(path string) *socketio.Namespace {
if ns, exists := m.namespaces[path]; exists {
return ns
}
ns := m.client.Of(path)
m.namespaces[path] = ns
return ns
}
func (m *NamespaceManager) Close() error {
return m.client.Close()
}
// Verwendung
manager := NewNamespaceManager("ws://localhost:3000")
defer manager.Close()
chat := manager.Get("/chat")
chat.On("message", handleChatMessage)
api := manager.Get("/api")
api.On("data", handleAPIData)
Muster: Namespace-Wrapper¶
Umschließen Sie Namespaces mit zusätzlicher Logik:
type ChatNamespace struct {
ns *socketio.Namespace
username string
room string
}
func NewChatNamespace(client *socketio.Socket, username string) *ChatNamespace {
cn := &ChatNamespace{
ns: client.Of("/chat"),
username: username,
}
cn.setupHandlers()
return cn
}
func (cn *ChatNamespace) setupHandlers() {
cn.ns.OnConnect(func() {
fmt.Printf("%s mit Chat verbunden\n", cn.username)
})
cn.ns.On("message", func(data ...interface{}) {
user := data[0].(string)
msg := data[1].(string)
fmt.Printf("[%s] %s: %s\n", cn.room, user, msg)
})
}
func (cn *ChatNamespace) JoinRoom(room string) {
cn.room = room
cn.ns.Emit("join-room", room)
}
func (cn *ChatNamespace) SendMessage(message string) {
cn.ns.Emit("message", cn.username, message)
}
func (cn *ChatNamespace) LeaveRoom() {
if cn.room != "" {
cn.ns.Emit("leave-room", cn.room)
cn.room = ""
}
}
// Verwendung
client := socketio.New("ws://localhost:3000")
chat := NewChatNamespace(client, "Max")
chat.JoinRoom("general")
chat.SendMessage("Hallo zusammen!")
Muster: Namespace mit Zustand¶
Halten Sie namespaces-spezifischen Zustand vor:
type APINamespace struct {
ns *socketio.Namespace
subscriptions []string
cache map[string]interface{}
mu sync.RWMutex
}
func NewAPINamespace(client *socketio.Socket) *APINamespace {
api := &APINamespace{
ns: client.Of("/api"),
cache: make(map[string]interface{}),
}
api.setupHandlers()
return api
}
func (a *APINamespace) setupHandlers() {
a.ns.OnConnect(func() {
// Automatisch neu abonnieren bei Wiederverbindung
if len(a.subscriptions) > 0 {
a.ns.Emit("subscribe", a.subscriptions)
}
})
a.ns.On("update", func(data ...interface{}) {
key := data[0].(string)
value := data[1]
a.mu.Lock()
a.cache[key] = value
a.mu.Unlock()
fmt.Printf("Cache aktualisiert: %s\n", key)
})
}
func (a *APINamespace) Subscribe(topics []string) {
a.subscriptions = append(a.subscriptions, topics...)
a.ns.Emit("subscribe", topics)
}
func (a *APINamespace) Get(key string) (interface{}, bool) {
a.mu.RLock()
defer a.mu.RUnlock()
value, exists := a.cache[key]
return value, exists
}
// Verwendung
client := socketio.New("ws://localhost:3000")
api := NewAPINamespace(client)
api.Subscribe([]string{"users", "posts", "comments"})
// Cache verwenden
if value, ok := api.Get("users"); ok {
fmt.Println("Benutzer:", value)
}
Muster: Dynamische Namespaces¶
Erstellen und zerstören Sie Namespaces dynamisch:
type DynamicNamespaceManager struct {
client *socketio.Socket
namespaces sync.Map // map[string]*socketio.Namespace
}
func NewDynamicNamespaceManager(url string) *DynamicNamespaceManager {
return &DynamicNamespaceManager{
client: socketio.New(url),
}
}
func (m *DynamicNamespaceManager) Join(path string) *socketio.Namespace {
if ns, loaded := m.namespaces.LoadOrStore(path, m.client.Of(path)); loaded {
return ns.(*socketio.Namespace)
}
ns := m.client.Of(path)
m.namespaces.Store(path, ns)
fmt.Printf("Namespace erstellt: %s\n", path)
return ns
}
func (m *DynamicNamespaceManager) Leave(path string) {
if ns, ok := m.namespaces.LoadAndDelete(path); ok {
fmt.Printf("Namespace entfernt: %s\n", path)
// Hinweis: Es gibt keine Close()-Methode für Namespace,
// einfach den Namespace nicht mehr verwenden
_ = ns
}
}
func (m *DynamicNamespaceManager) List() []string {
var paths []string
m.namespaces.Range(func(key, value interface{}) bool {
paths = append(paths, key.(string))
return true
})
return paths
}
// Verwendung
manager := NewDynamicNamespaceManager("ws://localhost:3000")
// Benutzer tritt einem Raum bei
roomNs := manager.Join("/rooms/room-123")
roomNs.On("message", handleRoomMessage)
// Benutzer verlässt den Raum
manager.Leave("/rooms/room-123")
// Aktive Räume anzeigen
fmt.Println("Aktive Räume:", manager.List())
Best Practices¶
1. Klare Organisation¶
Verwenden Sie klare und konsistente Namenskonventionen:
// ✅ Gut: klare und hierarchische Struktur
client.Of("/chat/public")
client.Of("/chat/private")
client.Of("/api/v1")
client.Of("/api/v2")
client.Of("/admin/users")
client.Of("/admin/settings")
// ❌ Schlecht: inkonsistente Namen
client.Of("/chat")
client.Of("/PRIVATE-CHAT")
client.Of("/api_v1")
client.Of("/AdminPanel")
2. Verbindungsverwaltung pro Namespace¶
Jeder Namespace sollte seine eigene Verbindung verwalten:
chat := client.Of("/chat")
api := client.Of("/api")
// ✅ Gut: jeder Namespace verwaltet seinen Zustand
chat.OnConnect(func() {
chat.Emit("join", "general")
})
api.OnConnect(func() {
api.Emit("authenticate", token)
})
// ❌ Schlecht: annehmen, dass alle gleichzeitig verbinden
client.OnConnect(func() {
// Dies wird nur für den Standard-Namespace ausgeführt
chat.Emit("join", "general") // Kann fehlschlagen, wenn /chat nicht verbunden ist
})
3. Namespace-Explosion vermeiden¶
Erstellen Sie nicht unnötig zu viele Namespaces:
// ✅ Gut: Namespaces nach Kategorie
chat := client.Of("/chat")
chat.Emit("join-room", "general")
chat.Emit("join-room", "random")
// ❌ Schlecht: ein Namespace pro Raum
general := client.Of("/chat/general")
random := client.Of("/chat/random")
tech := client.Of("/chat/tech")
// ... 100 weitere
4. Namespaces dokumentieren¶
Dokumentieren Sie, was jeder Namespace tut:
// Namespace /chat: Echtzeit-Messaging
// Events: message, user-joined, user-left, typing
// Authentifizierung: Erforderlich
chat := client.Of("/chat")
// Namespace /api: Echtzeit-Daten
// Events: update, delete, create
// Authentifizierung: JWT-Token
api := client.Of("/api")
// Namespace /admin: Administratorbereich
// Events: stats, user-action, system-alert
// Authentifizierung: Admin-Token
admin := client.Of("/admin")
5. Testing pro Namespace¶
Testen Sie jeden Namespace unabhängig:
func TestChatNamespace(t *testing.T) {
client := socketio.New("ws://localhost:3000")
defer client.Close()
chat := client.Of("/chat")
var received bool
chat.On("welcome", func(data ...interface{}) {
received = true
})
// Auf Verbindung warten
time.Sleep(100 * time.Millisecond)
if !received {
t.Error("Welcome-Event nicht empfangen")
}
}
6. Namespace-Cleanup¶
Bereinigen Sie Namespaces, wenn Sie sie nicht mehr benötigen:
type RoomManager struct {
client *socketio.Socket
rooms sync.Map
}
func (rm *RoomManager) JoinRoom(roomId string) {
ns := rm.client.Of("/rooms/" + roomId)
rm.rooms.Store(roomId, ns)
ns.OnConnect(func() {
fmt.Printf("Mit Raum %s verbunden\n", roomId)
})
}
func (rm *RoomManager) LeaveRoom(roomId string) {
if ns, ok := rm.rooms.LoadAndDelete(roomId); ok {
// Der Namespace wird keine Events mehr empfangen
fmt.Printf("Raum %s wird verlassen\n", roomId)
_ = ns
}
}
func (rm *RoomManager) Cleanup() {
rm.rooms.Range(func(key, value interface{}) bool {
rm.rooms.Delete(key)
return true
})
}
Vollständiges Beispiel: Multi-Raum-Chat-System¶
package main
import (
"fmt"
"sync"
socketio "github.com/arcaela/socket.io-client-go"
)
type ChatSystem struct {
client *socketio.Socket
user string
rooms map[string]*socketio.Namespace
roomsMutex sync.RWMutex
}
func NewChatSystem(url, username string) *ChatSystem {
return &ChatSystem{
client: socketio.New(url),
user: username,
rooms: make(map[string]*socketio.Namespace),
}
}
func (cs *ChatSystem) JoinRoom(roomName string) error {
cs.roomsMutex.Lock()
defer cs.roomsMutex.Unlock()
if _, exists := cs.rooms[roomName]; exists {
return fmt.Errorf("Sie sind bereits im Raum %s", roomName)
}
// Namespace für den Raum erstellen
room := cs.client.Of("/chat/" + roomName)
// Handler konfigurieren
room.OnConnect(func() {
fmt.Printf("✅ Mit Raum verbunden: %s\n", roomName)
room.Emit("join", cs.user)
})
room.OnDisconnect(func(reason string) {
fmt.Printf("❌ Von Raum %s getrennt: %s\n", roomName, reason)
})
room.On("message", func(data ...interface{}) {
user := data[0].(string)
msg := data[1].(string)
fmt.Printf("[%s] %s: %s\n", roomName, user, msg)
})
room.On("user-joined", func(data ...interface{}) {
user := data[0].(string)
fmt.Printf("[%s] 👤 %s ist beigetreten\n", roomName, user)
})
room.On("user-left", func(data ...interface{}) {
user := data[0].(string)
fmt.Printf("[%s] 👋 %s hat verlassen\n", roomName, user)
})
cs.rooms[roomName] = room
return nil
}
func (cs *ChatSystem) LeaveRoom(roomName string) error {
cs.roomsMutex.Lock()
defer cs.roomsMutex.Unlock()
room, exists := cs.rooms[roomName]
if !exists {
return fmt.Errorf("Sie sind nicht im Raum %s", roomName)
}
room.Emit("leave", cs.user)
delete(cs.rooms, roomName)
fmt.Printf("Sie haben den Raum verlassen: %s\n", roomName)
return nil
}
func (cs *ChatSystem) SendMessage(roomName, message string) error {
cs.roomsMutex.RLock()
room, exists := cs.rooms[roomName]
cs.roomsMutex.RUnlock()
if !exists {
return fmt.Errorf("Sie sind nicht im Raum %s", roomName)
}
return room.Emit("message", cs.user, message)
}
func (cs *ChatSystem) ListRooms() []string {
cs.roomsMutex.RLock()
defer cs.roomsMutex.RUnlock()
rooms := make([]string, 0, len(cs.rooms))
for name := range cs.rooms {
rooms = append(rooms, name)
}
return rooms
}
func (cs *ChatSystem) Close() error {
return cs.client.Close()
}
func main() {
chat := NewChatSystem("ws://localhost:3000", "Max")
defer chat.Close()
// Mehreren Räumen beitreten
chat.JoinRoom("general")
chat.JoinRoom("tech")
chat.JoinRoom("random")
// Nachrichten senden
chat.SendMessage("general", "Hallo zusammen!")
chat.SendMessage("tech", "Benutzt jemand Go?")
// Aktive Räume anzeigen
fmt.Println("Aktive Räume:", chat.ListRooms())
// Einen Raum verlassen
chat.LeaveRoom("random")
select {}
}