# 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)