Menu
Zurück zum Blog
2 min read
Performance

Server-Sent Events vs. WebSockets: Die richtige Wahl für Real-Time

Detaillierter Vergleich von SSE und WebSockets. Unidirektionale vs. bidirektionale Kommunikation, Use Cases und Performance-Analyse.

Server-Sent EventsWebSocketSSEReal-TimeStreamingEventSource
Server-Sent Events vs. WebSockets: Die richtige Wahl für Real-Time

Server-Sent Events vs. WebSockets: Die richtige Wahl für Real-Time

Meta-Description: Detaillierter Vergleich von SSE und WebSockets. Unidirektionale vs. bidirektionale Kommunikation, Use Cases und Performance-Analyse.

Keywords: Server-Sent Events, WebSocket, SSE, Real-Time, Streaming, EventSource, HTTP Streaming, Push Notifications


Einführung

Beide Technologien ermöglichen Server-Push – aber sie lösen unterschiedliche Probleme. SSE für einfaches Streaming, WebSockets für bidirektionale Kommunikation.


Quick Comparison

┌─────────────────────────────────────────────────────────────┐
│                    SSE vs. WEBSOCKET                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Server-Sent Events (SSE)       WebSocket                  │
│  ─────────────────────────      ─────────────────────      │
│                                                             │
│  Direction:                     Direction:                  │
│  Server → Client (only)         Server ↔ Client (both)     │
│                                                             │
│  Protocol:                      Protocol:                   │
│  HTTP/1.1 or HTTP/2             ws:// or wss://            │
│                                                             │
│  Reconnection:                  Reconnection:               │
│  Built-in automatic             Manual implementation       │
│                                                             │
│  Browser Support:               Browser Support:            │
│  All modern (no IE)             All modern + IE10+         │
│                                                             │
│  Use Cases:                     Use Cases:                  │
│  - LLM Streaming                - Chat Applications        │
│  - Live Feeds                   - Gaming                   │
│  - Notifications                - Collaborative Editing    │
│  - Stock Tickers                - Voice/Video              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Detaillierter Vergleich

AspektSSEWebSocket
**Richtung**Unidirektional (Server→Client)Bidirektional
**Protokoll**HTTPWS/WSS
**Datenformat**Text (UTF-8)Text + Binary
**Reconnection**AutomatischManuell
**Event IDs**Built-inManuell
**Proxy/Firewall**Besser (HTTP)Problematisch
**HTTP/2 Multiplexing**JaNein (eigene Connection)
**Max Connections**Browser-Limit (~6/Domain)Unbegrenzt
**Memory Overhead**NiedrigerHöher
**Setup-Komplexität**EinfachKomplexer

SSE Implementation

Server (Node.js/Express)

// src/sse/server.ts
import express from 'express';

const app = express();

app.get('/events', (req, res) => {
  // SSE Headers
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');
  res.setHeader('Access-Control-Allow-Origin', '*');

  // Flushes für sofortige Delivery
  res.flushHeaders();

  // Client-ID für Tracking
  const clientId = Date.now();
  console.log(`SSE client connected: ${clientId}`);

  // Event senden
  const sendEvent = (event: string, data: any, id?: string) => {
    if (id) res.write(`id: ${id}\n`);
    res.write(`event: ${event}\n`);
    res.write(`data: ${JSON.stringify(data)}\n\n`);
  };

  // Initial Event
  sendEvent('connected', { clientId });

  // Periodische Updates
  const interval = setInterval(() => {
    sendEvent('heartbeat', { timestamp: Date.now() });
  }, 30000);

  // Cleanup bei Disconnect
  req.on('close', () => {
    clearInterval(interval);
    console.log(`SSE client disconnected: ${clientId}`);
  });
});

app.listen(3000);

Client (Browser)

// src/sse/client.ts
class SSEClient {
  private eventSource: EventSource | null = null;
  private reconnectAttempts = 0;

  connect(url: string) {
    this.eventSource = new EventSource(url);

    this.eventSource.onopen = () => {
      console.log('SSE connected');
      this.reconnectAttempts = 0;
    };

    this.eventSource.onerror = (error) => {
      console.error('SSE error:', error);
      // EventSource reconnects automatically
    };

    // Named Events
    this.eventSource.addEventListener('connected', (event) => {
      const data = JSON.parse(event.data);
      console.log('Client ID:', data.clientId);
    });

    this.eventSource.addEventListener('heartbeat', (event) => {
      const data = JSON.parse(event.data);
      console.log('Heartbeat:', data.timestamp);
    });

    // Generic message event
    this.eventSource.onmessage = (event) => {
      console.log('Message:', event.data);
    };
  }

  disconnect() {
    this.eventSource?.close();
  }
}

SSE für LLM Streaming

Perfekter Use Case: AI Response Streaming

// src/api/chat.ts
import Anthropic from '@anthropic-ai/sdk';

app.post('/api/chat/stream', async (req, res) => {
  const { message } = req.body;

  // SSE Headers
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  const anthropic = new Anthropic();

  try {
    const stream = await anthropic.messages.stream({
      model: 'claude-3-haiku-20240307',
      max_tokens: 1000,
      messages: [{ role: 'user', content: message }]
    });

    for await (const event of stream) {
      if (event.type === 'content_block_delta') {
        res.write(`event: delta\n`);
        res.write(`data: ${JSON.stringify({ text: event.delta.text })}\n\n`);
      }
    }

    // Stream Ende
    res.write(`event: done\n`);
    res.write(`data: {}\n\n`);
    res.end();

  } catch (error) {
    res.write(`event: error\n`);
    res.write(`data: ${JSON.stringify({ error: error.message })}\n\n`);
    res.end();
  }
});

Client für LLM Streaming

// src/hooks/useStreamingChat.ts
export function useStreamingChat() {
  const [response, setResponse] = useState('');
  const [isStreaming, setIsStreaming] = useState(false);

  const sendMessage = async (message: string) => {
    setResponse('');
    setIsStreaming(true);

    const eventSource = new EventSource(
      `/api/chat/stream?message=${encodeURIComponent(message)}`
    );

    eventSource.addEventListener('delta', (event) => {
      const { text } = JSON.parse(event.data);
      setResponse(prev => prev + text);
    });

    eventSource.addEventListener('done', () => {
      eventSource.close();
      setIsStreaming(false);
    });

    eventSource.addEventListener('error', (event) => {
      console.error('Stream error');
      eventSource.close();
      setIsStreaming(false);
    });
  };

  return { response, isStreaming, sendMessage };
}

WebSocket Implementation

Server

// src/websocket/server.ts
import { WebSocketServer, WebSocket } from 'ws';

const wss = new WebSocketServer({ port: 3001 });

wss.on('connection', (ws: WebSocket) => {
  console.log('WebSocket client connected');

  // Bidirektionale Kommunikation
  ws.on('message', (data) => {
    const message = JSON.parse(data.toString());

    // Echo oder Processing
    ws.send(JSON.stringify({
      type: 'response',
      data: `Received: ${message.content}`
    }));
  });

  // Server-initiated Push
  const interval = setInterval(() => {
    if (ws.readyState === WebSocket.OPEN) {
      ws.send(JSON.stringify({
        type: 'heartbeat',
        timestamp: Date.now()
      }));
    }
  }, 30000);

  ws.on('close', () => {
    clearInterval(interval);
    console.log('WebSocket client disconnected');
  });
});

Client

// src/websocket/client.ts
class WebSocketClient {
  private ws: WebSocket | null = null;
  private reconnectTimeout: NodeJS.Timeout | null = null;

  connect(url: string) {
    this.ws = new WebSocket(url);

    this.ws.onopen = () => {
      console.log('WebSocket connected');
    };

    this.ws.onmessage = (event) => {
      const message = JSON.parse(event.data);
      this.handleMessage(message);
    };

    this.ws.onclose = () => {
      console.log('WebSocket disconnected');
      this.scheduleReconnect();
    };

    this.ws.onerror = (error) => {
      console.error('WebSocket error:', error);
    };
  }

  send(data: any) {
    if (this.ws?.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify(data));
    }
  }

  private handleMessage(message: any) {
    switch (message.type) {
      case 'response':
        console.log('Response:', message.data);
        break;
      case 'heartbeat':
        console.log('Heartbeat:', message.timestamp);
        break;
    }
  }

  private scheduleReconnect() {
    this.reconnectTimeout = setTimeout(() => {
      this.connect(this.ws?.url || '');
    }, 3000);
  }

  disconnect() {
    if (this.reconnectTimeout) {
      clearTimeout(this.reconnectTimeout);
    }
    this.ws?.close();
  }
}

Entscheidungsmatrix

function chooseProtocol(requirements: Requirements): 'SSE' | 'WebSocket' {
  // WebSocket wenn bidirektional benötigt
  if (requirements.bidirectional) {
    return 'WebSocket';
  }

  // WebSocket für Binary Data
  if (requirements.binaryData) {
    return 'WebSocket';
  }

  // WebSocket für viele Connections
  if (requirements.connectionsPerDomain > 6) {
    return 'WebSocket';
  }

  // SSE für einfaches Streaming
  if (requirements.useCase === 'llm-streaming' ||
      requirements.useCase === 'notifications' ||
      requirements.useCase === 'live-feed') {
    return 'SSE';
  }

  // SSE für bessere Proxy-Kompatibilität
  if (requirements.behindCorporateProxy) {
    return 'SSE';
  }

  // Default: SSE (einfacher)
  return 'SSE';
}

Use Case Empfehlungen

Use CaseEmpfehlungGrund
**LLM Response Streaming**SSEUnidirektional, einfach
**Chat Application**WebSocketBidirektional nötig
**Live Notifications**SSEServer-Push genügt
**Collaborative Editing**WebSocketEchtzzeit-Sync nötig
**Stock Ticker**SSENur Server→Client
**Online Gaming**WebSocketLow-Latency bidirektional
**File Upload Progress**WebSocketClient muss auch senden
**Social Feed Updates**SSEEinfacher Server-Push

Hybrid Approach

// Kombiniere beide für optimale Ergebnisse
class HybridRealtime {
  private sse: EventSource | null = null;
  private ws: WebSocket | null = null;

  connect(sseUrl: string, wsUrl: string) {
    // SSE für Server-Push (Notifications, Updates)
    this.sse = new EventSource(sseUrl);
    this.sse.addEventListener('notification', this.handleNotification);
    this.sse.addEventListener('update', this.handleUpdate);

    // WebSocket für bidirektionale Kommunikation (Chat, Actions)
    this.ws = new WebSocket(wsUrl);
    this.ws.onmessage = this.handleWebSocketMessage;
  }

  // Sende nur über WebSocket
  sendMessage(content: string) {
    this.ws?.send(JSON.stringify({ type: 'chat', content }));
  }

  // Empfange über beide Kanäle
  private handleNotification = (event: MessageEvent) => {
    // SSE Notification
  };

  private handleWebSocketMessage = (event: MessageEvent) => {
    // WebSocket Response
  };
}

Fazit

Wähle SSE wenn:

  • Nur Server→Client Kommunikation nötig
  • LLM/AI Response Streaming
  • Einfachheit gewünscht
  • Proxy-Kompatibilität wichtig

Wähle WebSocket wenn:

  • Bidirektionale Kommunikation nötig
  • Binary Data übertragen wird
  • Sehr niedrige Latenz kritisch
  • Viele simultane Verbindungen

Für LLM-Streaming ist SSE der klare Gewinner – einfacher, effizienter und perfekt für den Use Case.


Bildprompts

  1. "Two data streams - one flowing one direction, one flowing both ways, comparison visualization"
  2. "Server pushing data to multiple clients, server-sent events concept, clean diagram"
  3. "Bidirectional communication tunnel between devices, WebSocket visualization, tech art"

Quellen