# 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

| Aspekt | SSE | WebSocket |
|--------|-----|-----------|
| **Richtung** | Unidirektional (Server→Client) | Bidirektional |
| **Protokoll** | HTTP | WS/WSS |
| **Datenformat** | Text (UTF-8) | Text + Binary |
| **Reconnection** | Automatisch | Manuell |
| **Event IDs** | Built-in | Manuell |
| **Proxy/Firewall** | Besser (HTTP) | Problematisch |
| **HTTP/2 Multiplexing** | Ja | Nein (eigene Connection) |
| **Max Connections** | Browser-Limit (~6/Domain) | Unbegrenzt |
| **Memory Overhead** | Niedriger | Höher |
| **Setup-Komplexität** | Einfach | Komplexer |

---

## SSE Implementation

### Server (Node.js/Express)

```typescript
// 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)

```typescript
// 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

```typescript
// 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

```typescript
// 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

```typescript
// 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

```typescript
// 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

```typescript
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 Case | Empfehlung | Grund |
|----------|------------|-------|
| **LLM Response Streaming** | SSE | Unidirektional, einfach |
| **Chat Application** | WebSocket | Bidirektional nötig |
| **Live Notifications** | SSE | Server-Push genügt |
| **Collaborative Editing** | WebSocket | Echtzzeit-Sync nötig |
| **Stock Ticker** | SSE | Nur Server→Client |
| **Online Gaming** | WebSocket | Low-Latency bidirektional |
| **File Upload Progress** | WebSocket | Client muss auch senden |
| **Social Feed Updates** | SSE | Einfacher Server-Push |

---

## Hybrid Approach

```typescript
// 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

- [MDN: Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)
- [MDN: WebSocket API](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
- [HTTP/2 Server Push vs SSE](https://www.smashingmagazine.com/2018/02/sse-websockets-data-flow-http2/)
- [Choosing Between SSE and WebSocket](https://ably.com/blog/websockets-vs-sse)
