2 min read
PerformanceServer-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
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)
// 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 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
// 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
- "Two data streams - one flowing one direction, one flowing both ways, comparison visualization"
- "Server pushing data to multiple clients, server-sent events concept, clean diagram"
- "Bidirectional communication tunnel between devices, WebSocket visualization, tech art"