Files
Portfolio/blog-posts/27-sse-vs-websocket.md
damjan_savicandClaude Opus 4.5 43484c5023 Add blog posts, cleanup unused files, update components
- Add 100 blog posts covering AI, development, and tech topics
- Add .env.example for environment configuration
- Add accessibility and lighthouse audit scripts
- Remove obsolete SEO reports and temporary files
- Remove dev-dist build artifacts and backup files
- Remove unused portrait images (moved/consolidated elsewhere)
- Update contact form and component improvements

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 11:42:11 +01:00

471 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)