Zum Hauptinhalt springen

πŸ”΄ WebSocket vs SSE vs Long Polling

πŸ“– Definition​

Echtzeit-Kommunikation ist eine Technologie, die den sofortigen Datenaustausch zwischen Server und Client ermΓΆglicht. WebSocket bietet bidirektionale Echtzeit-Kommunikation, SSE (Server-Sent Events) sendet Daten nur vom Server zum Client, und Long Polling ist eine Echtzeit-Kommunikationsmethode, die HTTP verwendet. Jede hat Vor- und Nachteile, daher sollte die Auswahl je nach Nutzungsszenario getroffen werden.

🎯 Verstehen durch Analogien​

Telefon vs Radio vs Messenger​

Normales HTTP = Post
Du: "Hallo!" (Brief senden)
↓ (nach mehreren Tagen)
Freund: "Hallo!" (Antwort)
↓ (nach mehreren Tagen)
Du: "Wie geht's?" (noch ein Brief)

- Langsam
- Jedes Mal neue Verbindung
- Nicht in Echtzeit

Long Polling = Telefon (warten auf Anruf)
Du: "Sag Bescheid, wenn was ist!" (anrufen und warten)
↓ (lange warten...)
Freund: "Ich hab jetzt was zu sagen!" (Antwort)
↓ (Anruf beendet)
Du: "Sag wieder Bescheid, wenn was ist!" (erneut anrufen)

- HTTP-basiert
- Verbindung halten β†’ Antwort β†’ Neuverbindung
- Ineffizient

SSE = Radiosendung
Freund: "Hallo zusammen!" (Sendung beginnt)
"Das Wetter heute ist..." (fortlaufende Übertragung)
"Die nÀchste Nachricht ist..." (fortlaufende Übertragung)
Du: (nur zuhΓΆren)

- Server β†’ Client (unidirektional)
- Verbindung gehalten
- Einfach

WebSocket = Videoanruf
Du: "Hallo!" (sofortiges Senden)
Freund: "SchΓΆn dich zu sehen!" (sofortige Antwort)
Du: "Was machst du?" (sofortiges Senden)
Freund: "Programmieren!" (sofortige Antwort)

- Bidirektional in Echtzeit
- Verbindung gehalten
- Schnell und effizient

Restaurant-Bestellung​

Normales HTTP = Selbstbedienung
1. Zum Tresen gehen und Essen bestellen
2. Warten
3. Essen erhalten und zum Tisch
4. Wieder zum Tresen und GetrΓ€nk bestellen
5. Wieder warten

Long Polling = Klingel drΓΌcken und warten
1. Klingel drΓΌcken und auf Mitarbeiter warten (Verbindung halten)
2. Mitarbeiter kommt β†’ "Ihre Bestellung bitte"
3. Bestellen und Klingel erneut drΓΌcken
4. Wieder warten...

SSE = KΓΌchendisplay
KΓΌche: "Kunde 1, Ihr Essen ist fertig!"
KΓΌche: "Kunde 2, wird vorbereitet!"
KΓΌche: "Kunde 3, kommt bald!"
Du: (nur zuhΓΆren)

WebSocket = Tischservice
Du: "Wasser, bitte"
Mitarbeiter: "Ja, bringe ich Ihnen"
Du: "Kimchi auch"
Mitarbeiter: "Bringe ich sofort"
Mitarbeiter: "Ihr Essen"
Du: "Danke"

- Freie Konversation
- Schnelle Antwort

βš™οΈ Funktionsweise​

1. Normales HTTP vs Echtzeit-Kommunikation​

========== Normales HTTP (Request-Response) ==========

Client Server
β”‚ β”‚
β”‚ 1. Verbindung (Request) β”‚
│────────────────────────>β”‚
β”‚ β”‚
β”‚ β”‚ Verarbeitung...
β”‚ β”‚
β”‚ 2. Antwort (Response) β”‚
β”‚<────────────────────────│
β”‚ β”‚
β”‚ Verbindung geschlossen β”‚
β•³ β•³

β”‚ 3. Neu verbinden β”‚
│────────────────────────>β”‚
β”‚ β”‚

Merkmale:
- Antwort nur wenn Client anfrΓ€gt
- Jedes Mal neue Verbindung
- Server kann nicht zuerst senden
- Nicht in Echtzeit

========== Echtzeit-Kommunikation ==========

Client Server
β”‚ β”‚
β”‚ Verbindung β”‚
β”‚<───────────────────────>β”‚
β”‚ β”‚
β”‚ Bidirektionale Kommunikation gehalten β”‚
β”‚<───────────────────────>β”‚
β”‚ β”‚
β”‚ Datenaustausch β”‚
β”‚<───────────────────────>β”‚
β”‚ β”‚

Merkmale:
- Verbindung gehalten
- Server kann zuerst senden
- Echtzeit mΓΆglich

2. Long Polling​

Client                              Server
β”‚ β”‚
β”‚ 1. Anfrage (Neue Daten verfΓΌgbar?)β”‚
│──────────────────────────────────>β”‚
β”‚ β”‚
β”‚ Verbindung gehalten (warten...)β”‚
β”‚ β”‚
β”‚ β”‚ Keine Daten...
β”‚ β”‚ Weiter warten...
β”‚ β”‚
β”‚ β”‚ Neue Daten!
β”‚ β”‚
β”‚ 2. Antwort (Hier sind Daten) β”‚
β”‚<──────────────────────────────────│
β”‚ β”‚
β”‚ Verbindung geschlossen β”‚
β•³ β•³
β”‚ β”‚
β”‚ 3. Sofort neu verbinden β”‚
│──────────────────────────────────>β”‚
β”‚ β”‚
β”‚ Wieder warten... β”‚

Prozess:
1. Client β†’ Server: "Neue Daten?"
2. Server: Warten bis Daten verfΓΌgbar
3. Daten verfΓΌgbar β†’ Antwort
4. Verbindung geschlossen
5. Sofort neu verbinden (wiederholen)

Vorteile:
βœ… HTTP-basiert (vorhandene Infrastruktur nutzen)
βœ… Keine Firewall-Probleme
βœ… Einfache Implementierung

Nachteile:
❌ Ineffizient (stÀndiges Neuverbinden)
❌ Hohe Serverlast
❌ Header-Overhead

3. Server-Sent Events (SSE)​

Client                              Server
β”‚ β”‚
β”‚ 1. Verbindungsanfrage β”‚
│──────────────────────────────────>β”‚
β”‚ β”‚
β”‚ 2. Verbindung halten (Stream-Start)β”‚
β”‚<══════════════════════════════════│
β”‚ β”‚
β”‚ 3. Daten-Push β”‚
β”‚<──────────────────────────────────│
β”‚ β”‚
β”‚ 4. Weiterer Daten-Push β”‚
β”‚<──────────────────────────────────│
β”‚ β”‚
β”‚ Verbindung gehalten... β”‚
β”‚<══════════════════════════════════│

Merkmale:
- Server β†’ Client (unidirektional)
- Verbindung gehalten
- HTTP-basiert
- Automatische Neuverbindung

Vorteile:
βœ… Einfach (im Browser integriert)
βœ… Automatische Neuverbindung
βœ… Mit Event-ID fortsetzbar
βœ… Effizient mit HTTP/2

Nachteile:
❌ Unidirektional (Server β†’ Client)
❌ Keine binÀren Daten
❌ Kein IE-Support

4. WebSocket​

Client                              Server
β”‚ β”‚
β”‚ 1. HTTP Upgrade-Anfrage β”‚
│──────────────────────────────────>β”‚
β”‚ β”‚
β”‚ 2. Upgrade-Genehmigung β”‚
β”‚<──────────────────────────────────│
β”‚ β”‚
β”‚ Umschalten auf WebSocket-Protokollβ”‚
β”‚<══════════════════════════════════>β”‚
β”‚ β”‚
β”‚ 3. Daten senden β”‚
│──────────────────────────────────>β”‚
β”‚ β”‚
β”‚ 4. Daten empfangen β”‚
β”‚<──────────────────────────────────│
β”‚ β”‚
β”‚ 5. Daten senden β”‚
│──────────────────────────────────>β”‚
β”‚ β”‚
β”‚ Bidirektionale Kommunikation fortsetzen...β”‚
β”‚<══════════════════════════════════>β”‚

Merkmale:
- Bidirektionale Echtzeit-Kommunikation
- Separates Protokoll (ws://, wss://)
- Verbindung gehalten
- Niedrige Latenz

Vorteile:
βœ… Echte Echtzeit
βœ… Bidirektionale Kommunikation
βœ… Niedriger Overhead
βœ… BinΓ€r-UnterstΓΌtzung

Nachteile:
❌ Komplex
❌ Mâgliche Proxy/Firewall-Probleme
❌ Serverlast (Verbindung halten)

πŸ’‘ Praktische Beispiele​

Long Polling Beispiel​

// ========== Server (Express.js) ==========
const express = require('express');
const app = express();

let messages = [];
let waitingClients = [];

// Nachricht hinzufΓΌgen (von anderer Stelle aufgerufen)
function addMessage(message) {
messages.push(message);

// Sofort auf wartende Clients antworten
waitingClients.forEach(client => {
client.json({ messages });
});
waitingClients = [];
}

// Long Polling Endpunkt
app.get('/messages', (req, res) => {
const lastId = parseInt(req.query.lastId) || 0;

// Sofort antworten wenn neue Nachrichten
if (messages.length > lastId) {
return res.json({ messages: messages.slice(lastId) });
}

// Sonst zur Warteliste hinzufΓΌgen (max 30 Sekunden)
waitingClients.push(res);

// 30 Sekunden Timeout
req.setTimeout(30000, () => {
const index = waitingClients.indexOf(res);
if (index > -1) {
waitingClients.splice(index, 1);
res.json({ messages: [] }); // Leere Antwort
}
});
});

app.listen(3000);

// ========== Client ==========
let lastMessageId = 0;

async function longPolling() {
while (true) {
try {
const response = await fetch(`/messages?lastId=${lastMessageId}`);
const data = await response.json();

// Neue Nachrichten verarbeiten
if (data.messages.length > 0) {
data.messages.forEach(msg => {
console.log('Neue Nachricht:', msg);
displayMessage(msg);
});
lastMessageId += data.messages.length;
}

// Sofort neu verbinden
await longPolling();
} catch (error) {
console.error('Fehler:', error);
// Nach 3 Sekunden erneut versuchen
await new Promise(resolve => setTimeout(resolve, 3000));
}
}
}

// Starten
longPolling();

// ========== Probleme ==========
/*
1. StΓ€ndiges Neuverbinden (ineffizient)
2. Hohe Netznutzung
3. Hohe Serverlast
4. Batterieverbrauch (mobil)
*/

Server-Sent Events (SSE) Beispiel​

// ========== Server (Express.js) ==========
const express = require('express');
const app = express();

app.use(express.static('public'));

// SSE Endpunkt
app.get('/events', (req, res) => {
// SSE Header setzen
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');

// CORS (falls nΓΆtig)
res.setHeader('Access-Control-Allow-Origin', '*');

// VerbindungsbestΓ€tigung
res.write('data: Connected\n\n');

// Client-ID
const clientId = Date.now();
console.log(`Client ${clientId} verbunden`);

// Alle 5 Sekunden Zeit senden
const intervalId = setInterval(() => {
const data = {
time: new Date().toLocaleTimeString(),
message: 'Hallo!'
};

// Im SSE-Format senden
res.write(`data: ${JSON.stringify(data)}\n\n`);
}, 5000);

// Client-Trennung behandeln
req.on('close', () => {
console.log(`Client ${clientId} getrennt`);
clearInterval(intervalId);
res.end();
});
});

// API fΓΌr Event-Publishing
const clients = [];

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

// Client speichern
clients.push(res);

req.on('close', () => {
const index = clients.indexOf(res);
clients.splice(index, 1);
});
});

// Nachricht an alle Clients senden
app.post('/broadcast', express.json(), (req, res) => {
const { message } = req.body;

clients.forEach(client => {
client.write(`data: ${JSON.stringify({ message })}\n\n`);
});

res.json({ success: true });
});

app.listen(3000);

// ========== Client (HTML) ==========
/*
<!DOCTYPE html>
<html>
<body>
<div id="messages"></div>

<script>
// SSE-Verbindung
const eventSource = new EventSource('/events');

// Nachrichten empfangen
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Daten empfangen:', data);

const div = document.getElementById('messages');
div.innerHTML += `<p>${data.time}: ${data.message}</p>`;
};

// Verbindung geΓΆffnet
eventSource.onopen = () => {
console.log('SSE verbunden');
};

// Fehlerbehandlung
eventSource.onerror = (error) => {
console.error('SSE-Fehler:', error);
if (eventSource.readyState === EventSource.CLOSED) {
console.log('SSE-Verbindung geschlossen');
}
};

// Verbindung schließen (beim Verlassen der Seite)
window.addEventListener('beforeunload', () => {
eventSource.close();
});
</script>
</body>
</html>
*/

// ========== Erweiterte Funktionen ==========

// 1. Event-Typ angeben
app.get('/events/typed', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');

// Verschiedene Event-Typen
setInterval(() => {
// Allgemeine Nachricht
res.write(`event: message\ndata: Hello\n\n`);

// Benachrichtigung
res.write(`event: notification\ndata: New notification!\n\n`);

// Update
res.write(`event: update\ndata: {"count": 10}\n\n`);
}, 5000);
});

// Event-Typ-spezifische Behandlung im Client
/*
eventSource.addEventListener('message', (e) => {
console.log('Nachricht:', e.data);
});

eventSource.addEventListener('notification', (e) => {
console.log('Benachrichtigung:', e.data);
});

eventSource.addEventListener('update', (e) => {
const data = JSON.parse(e.data);
console.log('Update:', data);
});
*/

// 2. Event-ID (bei Neuverbindung fortsetzen)
let eventId = 0;

app.get('/events/resumable', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');

const lastEventId = parseInt(req.headers['last-event-id']) || 0;
console.log('Letzte Event-ID:', lastEventId);

// Nur Events nach lastEventId senden
setInterval(() => {
eventId++;
res.write(`id: ${eventId}\ndata: Event ${eventId}\n\n`);
}, 1000);
});

// 3. Wiederholungszeit einstellen
app.get('/events/retry', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');

// Nach 5 Sekunden neu verbinden
res.write('retry: 5000\n');
res.write('data: Connected\n\n');
});

WebSocket Beispiel (Socket.io)​

// ========== Server (Socket.io) ==========
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');

const app = express();
const server = http.createServer(app);
const io = socketIo(server, {
cors: {
origin: '*'
}
});

app.use(express.static('public'));

// Verwaltung verbundener Benutzer
const users = new Map();

// WebSocket-Verbindung
io.on('connection', (socket) => {
console.log('Neuer Benutzer verbunden:', socket.id);

// Benutzerinformationen speichern
socket.on('register', (username) => {
users.set(socket.id, { username, socket });
console.log(`${username} registriert`);

// Alle Benutzer benachrichtigen
io.emit('user-joined', {
username,
totalUsers: users.size
});
});

// Nachricht empfangen
socket.on('chat-message', (message) => {
const user = users.get(socket.id);
console.log(`${user.username}: ${message}`);

// An alle Benutzer senden
io.emit('chat-message', {
username: user.username,
message,
timestamp: new Date().toISOString()
});
});

// Tipp-Anzeige
socket.on('typing', () => {
const user = users.get(socket.id);
socket.broadcast.emit('user-typing', user.username);
});

socket.on('stop-typing', () => {
const user = users.get(socket.id);
socket.broadcast.emit('user-stop-typing', user.username);
});

// Private Nachricht
socket.on('private-message', ({ to, message }) => {
const targetSocket = Array.from(users.values())
.find(u => u.username === to)?.socket;

if (targetSocket) {
const sender = users.get(socket.id);
targetSocket.emit('private-message', {
from: sender.username,
message
});
}
});

// Trennung
socket.on('disconnect', () => {
const user = users.get(socket.id);
if (user) {
console.log(`${user.username} getrennt`);
users.delete(socket.id);

// Alle Benutzer benachrichtigen
io.emit('user-left', {
username: user.username,
totalUsers: users.size
});
}
});
});

server.listen(3000, () => {
console.log('Server lΓ€uft: http://localhost:3000');
});

// ========== Client (HTML + Socket.io) ==========
/*
<!DOCTYPE html>
<html>
<head>
<script src="/socket.io/socket.io.js"></script>
</head>
<body>
<div id="chat">
<div id="messages"></div>
<input id="username" placeholder="Name" />
<input id="message" placeholder="Nachricht" />
<button onclick="sendMessage()">Senden</button>
</div>

<script>
// Socket.io-Verbindung
const socket = io('http://localhost:3000');

// Erfolgreich verbunden
socket.on('connect', () => {
console.log('Verbunden:', socket.id);
});

// Benutzer registrieren
function register() {
const username = document.getElementById('username').value;
socket.emit('register', username);
}

// Nachricht senden
function sendMessage() {
const message = document.getElementById('message').value;
socket.emit('chat-message', message);
document.getElementById('message').value = '';
}

// Nachricht empfangen
socket.on('chat-message', (data) => {
const div = document.getElementById('messages');
div.innerHTML += `
<p><strong>${data.username}:</strong> ${data.message}</p>
`;
});

// Benutzer beigetreten
socket.on('user-joined', (data) => {
console.log(`${data.username} beigetreten (gesamt ${data.totalUsers})`);
});

// Benutzer gegangen
socket.on('user-left', (data) => {
console.log(`${data.username} gegangen (gesamt ${data.totalUsers})`);
});

// Tippen
let typingTimeout;
document.getElementById('message').addEventListener('input', () => {
socket.emit('typing');

clearTimeout(typingTimeout);
typingTimeout = setTimeout(() => {
socket.emit('stop-typing');
}, 1000);
});

socket.on('user-typing', (username) => {
console.log(`${username} tippt...`);
});

// Trennung
socket.on('disconnect', () => {
console.log('Getrennt');
});

// Neuverbindung
socket.on('reconnect', () => {
console.log('Neu verbunden');
});
</script>
</body>
</html>
*/

Native WebSocket API​

// ========== Server (ws-Bibliothek) ==========
const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws) => {
console.log('Client verbunden');

// Nachricht empfangen
ws.on('message', (message) => {
console.log('Nachricht empfangen:', message.toString());

// An alle Clients senden
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(message.toString());
}
});
});

// Trennung
ws.on('close', () => {
console.log('Client getrennt');
});

// Fehler
ws.on('error', (error) => {
console.error('WebSocket-Fehler:', error);
});

// Willkommensnachricht
ws.send('Mit Server verbunden!');
});

console.log('WebSocket-Server lΓ€uft: ws://localhost:8080');

// ========== Client (Browser) ==========

// WebSocket-Verbindung
const ws = new WebSocket('ws://localhost:8080');

// Verbindung geΓΆffnet
ws.addEventListener('open', (event) => {
console.log('WebSocket verbunden');
ws.send('Hallo!');
});

// Nachricht empfangen
ws.addEventListener('message', (event) => {
console.log('Vom Server:', event.data);
});

// Fehler
ws.addEventListener('error', (error) => {
console.error('WebSocket-Fehler:', error);
});

// Trennung
ws.addEventListener('close', (event) => {
console.log('WebSocket getrennt');
if (event.code === 1000) {
console.log('Normale Trennung');
} else {
console.log('Abnormale Trennung:', event.code);
}
});

// Nachricht senden
function sendMessage(message) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(message);
} else {
console.error('WebSocket nicht geΓΆffnet');
}
}

// Verbindung schließen
function closeConnection() {
ws.close(1000, 'Normale Trennung');
}

// ========== BinΓ€rdatenΓΌbertragung ==========

// Datei senden
async function sendFile(file) {
const arrayBuffer = await file.arrayBuffer();
ws.send(arrayBuffer);
}

// BinΓ€rempfang auf Server
ws.addEventListener('message', (event) => {
if (event.data instanceof ArrayBuffer) {
console.log('BinΓ€rdaten empfangen:', event.data.byteLength, 'bytes');
} else {
console.log('Textdaten:', event.data);
}
});

// ========== Ping/Pong (Verbindung halten) ==========

// Server
wss.on('connection', (ws) => {
ws.isAlive = true;

ws.on('pong', () => {
ws.isAlive = true;
});
});

// Alle 30 Sekunden Ping
setInterval(() => {
wss.clients.forEach((ws) => {
if (ws.isAlive === false) {
return ws.terminate(); // Verbindung beenden wenn keine Antwort
}

ws.isAlive = false;
ws.ping();
});
}, 30000);

// ========== Automatische Neuverbindung ==========

let ws;
let reconnectAttempts = 0;
const maxReconnectAttempts = 10;

function connect() {
ws = new WebSocket('ws://localhost:8080');

ws.addEventListener('open', () => {
console.log('Verbunden');
reconnectAttempts = 0;
});

ws.addEventListener('close', (event) => {
console.log('Getrennt');

// Automatische Neuverbindung
if (reconnectAttempts < maxReconnectAttempts) {
reconnectAttempts++;
const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 30000);
console.log(`Neuverbindung in ${delay}ms...`);
setTimeout(connect, delay);
} else {
console.error('Neuverbindung fehlgeschlagen');
}
});

ws.addEventListener('error', (error) => {
console.error('Fehler:', error);
ws.close();
});
}

connect();

πŸ€” HΓ€ufig gestellte Fragen​

F1. Welche Methode soll ich wΓ€hlen?​

A:

βœ… Long Polling wΓ€hlen wenn:
β”œβ”€ Umgebung ohne WebSocket/SSE-UnterstΓΌtzung
β”œβ”€ Legacy-System
β”œβ”€ Einfache Benachrichtigungen
└─ Z.B.: Alte Browser-UnterstΓΌtzung, einfaches Polling

Anwendungsbeispiele:
- Aktienkurse (seltene Updates)
- E-Mail-PrΓΌfung
- Einfache Benachrichtigungen

βœ… SSE wΓ€hlen wenn:
β”œβ”€ Unidirektional Server β†’ Client
β”œβ”€ Echtzeit-Updates
β”œβ”€ Einfache Implementierung gewΓΌnscht
β”œβ”€ Automatische Neuverbindung benΓΆtigt
└─ Z.B.: Newsfeed, Benachrichtigungen, Dashboard

Anwendungsbeispiele:
- Newsfeed (Echtzeit-Updates)
- Aktiencharts (Server-Push)
- Fortschrittsanzeige
- Log-Streaming
- Server-Monitoring

βœ… WebSocket wΓ€hlen wenn:
β”œβ”€ Bidirektionale Echtzeit-Kommunikation
β”œβ”€ Niedrige Latenz wichtig
β”œβ”€ HΓ€ufiger Nachrichtenaustausch
β”œβ”€ BinΓ€rdaten
└─ Z.B.: Chat, Spiele, Kollaborationstools

Anwendungsbeispiele:
- Chat-Anwendung
- Multiplayer-Spiele
- Kollaborative Dokumentenbearbeitung (Google Docs)
- Videokonferenz
- IoT-Echtzeitsteuerung

πŸ“Š Vergleichstabelle:

Merkmal | Long Polling | SSE | WebSocket
-------------|-------------|--------------|----------
Richtung | Bidirektional | Unidirektional | Bidirektional
Protokoll | HTTP | HTTP | WebSocket
Latenz | Hoch | Niedrig | Sehr niedrig
Overhead | Hoch | Mittel | Niedrig
Browser | Alle | Meiste | Alle
KomplexitΓ€t | Niedrig | Niedrig | Hoch
Neuverbindung| Manuell | Automatisch | Manuell
BinΓ€r | MΓΆglich | Nein | Ja

Empfehlung:
- Einfache Echtzeit: SSE
- Chat/Spiele: WebSocket
- Legacy-UnterstΓΌtzung: Long Polling

F2. Leistungsunterschied?​

A:

// ========== Benchmark-Beispiel ==========

// 1. Long Polling
// Pro Anfrage:
// - HTTP-Header: ~800 bytes
// - Neuverbindungszeit: ~50ms
// - Server-Ressourcen: 1 Thread pro Verbindung

// 100 gleichzeitige Benutzer:
// - Anfragen pro Sekunde: 100
// - DatenΓΌbertragung: 80KB/s (nur Header)
// - Serverlast: Hoch

// 2. SSE
// Pro Verbindung:
// - HTTP-Header: Nur 1x initial (~800 bytes)
// - Nachrichten-Overhead: ~10 bytes
// - Server-Ressourcen: Verbindung halten (Keep-Alive)

// 100 gleichzeitige Benutzer:
// - Initiale Verbindung: 80KB
// - Pro Nachricht: 1KB (ohne Header)
// - Serverlast: Mittel

// 3. WebSocket
// Pro Verbindung:
// - Upgrade-Header: Nur 1x (~500 bytes)
// - Frame-Overhead: 2~6 bytes
// - Server-Ressourcen: Verbindung halten

// 100 gleichzeitige Benutzer:
// - Initiale Verbindung: 50KB
// - Pro Nachricht: ~0.002KB (nur Frame)
// - Serverlast: Niedrig

// ========== Reale Messung ==========

// Test: 100 Benutzer senden 1 Nachricht pro Sekunde

// Long Polling
const longPollingBenchmark = {
antragenProSekunde: 100,
durchschnittlicheLatenz: '200ms',
bandbreite: '8MB/min',
serverCPU: '70%',
speicher: '500MB'
};

// SSE
const sseBenchmark = {
antragenProSekunde: 0, // Verbindung gehalten
durchschnittlicheLatenz: '10ms',
bandbreite: '600KB/min',
serverCPU: '30%',
speicher: '200MB'
};

// WebSocket
const webSocketBenchmark = {
antragenProSekunde: 0, // Verbindung gehalten
durchschnittlicheLatenz: '2ms',
bandbreite: '100KB/min',
serverCPU: '15%',
speicher: '100MB'
};

// ========== Praktische Optimierung ==========

// 1. Anzahl der Verbindungen begrenzen
const MAX_CONNECTIONS = 1000;

io.on('connection', (socket) => {
if (io.engine.clientsCount > MAX_CONNECTIONS) {
socket.disconnect();
return;
}
});

// 2. Nachrichtenkompression
const io = socketIo(server, {
perMessageDeflate: {
threshold: 1024 // Nur komprimieren wenn > 1KB
}
});

// 3. Batch-Verarbeitung
const messageQueue = [];

setInterval(() => {
if (messageQueue.length > 0) {
io.emit('batch', messageQueue);
messageQueue.length = 0;
}
}, 100); // Batch-Versand alle 100ms

// 4. Raum-Verwendung (Room)
socket.join('room1');
io.to('room1').emit('message', data); // Nur room1

// 5. BinΓ€rΓΌbertragung
// WebSocket ist mit BinΓ€r effizient
const buffer = Buffer.from('Hello');
socket.send(buffer); // Schneller als Text

// 6. Heartbeat-Optimierung
const HEARTBEAT_INTERVAL = 25000; // 25 Sekunden
const HEARTBEAT_TIMEOUT = 30000; // 30 Sekunden

setInterval(() => {
wss.clients.forEach((ws) => {
if (ws.isAlive === false) {
return ws.terminate();
}
ws.isAlive = false;
ws.ping();
});
}, HEARTBEAT_INTERVAL);

F3. Und bei mobilen Apps?​

A:

// ========== Mobile Überlegungen ==========

// 1. Batterieverbrauch
// Long Polling: ❌ Hoch (stÀndige Neuverbindung)
// SSE: ⚠️ Mittel
// WebSocket: βœ… Niedrig (Verbindung gehalten)

// 2. Netzwerkwechsel
// WiFi <-> Mobile Daten Wechsel

// Automatische Neuverbindung
let ws;

function connect() {
ws = new WebSocket('wss://api.example.com');

ws.onclose = () => {
// Exponentielles Backoff
const delay = Math.min(1000 * Math.pow(2, attempts), 30000);
setTimeout(connect, delay);
};
}

// Netzwerkstatus erkennen
window.addEventListener('online', () => {
console.log('Online - neu verbinden');
connect();
});

window.addEventListener('offline', () => {
console.log('Offline');
ws.close();
});

// 3. Hintergrundverarbeitung
// Was passiert, wenn App in Hintergrund geht?

// React Native Beispiel
import { AppState } from 'react-native';

AppState.addEventListener('change', (nextAppState) => {
if (nextAppState === 'background') {
// Hintergrund β†’ Verbindung halten oder schließen
ws.close();
} else if (nextAppState === 'active') {
// Vordergrund β†’ Neu verbinden
connect();
}
});

// 4. Datenspar-Modus
// Je nach Benutzereinstellung

const isDataSaverMode = await getDataSaverSetting();

if (isDataSaverMode) {
// Long Polling (seltener)
setInterval(poll, 60000); // Alle 1 Minute
} else {
// WebSocket (Echtzeit)
connect();
}

// 5. Push-Benachrichtigungs-Integration
// WebSocket + FCM/APNs Kombination

// App lΓ€uft: WebSocket
if (appIsActive) {
useWebSocket();
}

// Hintergrund: Push-Benachrichtigung
if (appIsBackground) {
usePushNotification();
}

// ========== React Native Beispiel ==========
import React, { useEffect, useState } from 'react';
import { View, Text } from 'react-native';

function ChatScreen() {
const [ws, setWs] = useState(null);
const [messages, setMessages] = useState([]);
const [isConnected, setIsConnected] = useState(false);

useEffect(() => {
const websocket = new WebSocket('wss://api.example.com');

websocket.onopen = () => {
console.log('Connected');
setIsConnected(true);
};

websocket.onmessage = (event) => {
const message = JSON.parse(event.data);
setMessages(prev => [...prev, message]);
};

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

websocket.onclose = () => {
console.log('Disconnected');
setIsConnected(false);

// Nach 3 Sekunden neu verbinden
setTimeout(() => {
// Neuverbindungslogik
}, 3000);
};

setWs(websocket);

// AufrΓ€umen
return () => {
websocket.close();
};
}, []);

const sendMessage = (text) => {
if (ws && isConnected) {
ws.send(JSON.stringify({ text }));
}
};

return (
<View>
<Text>Connected: {isConnected ? 'Yes' : 'No'}</Text>
{messages.map((msg, i) => (
<Text key={i}>{msg.text}</Text>
))}
</View>
);
}

F4. Wie absichern?​

A:

// ========== 1. HTTPS/WSS verwenden ==========

// ❌ HTTP/WS (Klartext-Übertragung)
const ws = new WebSocket('ws://api.example.com');

// βœ… HTTPS/WSS (verschlΓΌsselt)
const ws = new WebSocket('wss://api.example.com');

// ========== 2. Authentifizierung ==========

// Methode 1: JWT in URL (einfach aber nicht empfohlen)
const token = getJWTToken();
const ws = new WebSocket(`wss://api.example.com?token=${token}`);
// ⚠️ Risiko der Token-Offenlegung in URL

// Methode 2: Authentifizierung nach Verbindung (empfohlen)
const ws = new WebSocket('wss://api.example.com');

ws.onopen = () => {
// Authentifizierungsnachricht senden
ws.send(JSON.stringify({
type: 'auth',
token: getJWTToken()
}));
};

// Server
io.use((socket, next) => {
const token = socket.handshake.auth.token;

try {
const decoded = jwt.verify(token, SECRET_KEY);
socket.userId = decoded.userId;
next();
} catch (error) {
next(new Error('Authentifizierung fehlgeschlagen'));
}
});

// Methode 3: Cookie (HttpOnly)
// Client
const ws = new WebSocket('wss://api.example.com');
// Cookie wird automatisch gesendet

// Server
io.use((socket, next) => {
const cookies = parseCookies(socket.request.headers.cookie);
const sessionId = cookies.sessionId;

if (isValidSession(sessionId)) {
next();
} else {
next(new Error('Authentifizierung fehlgeschlagen'));
}
});

// ========== 3. CORS-Konfiguration ==========

const io = socketIo(server, {
cors: {
origin: 'https://myapp.com', // Nur spezifische Domain
credentials: true
}
});

// Mehrere Domains
const allowedOrigins = ['https://myapp.com', 'https://admin.myapp.com'];

const io = socketIo(server, {
cors: {
origin: (origin, callback) => {
if (allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('CORS abgelehnt'));
}
},
credentials: true
}
});

// ========== 4. Rate Limiting ==========

const rateLimitMap = new Map();

io.on('connection', (socket) => {
socket.on('message', (data) => {
const userId = socket.userId;
const now = Date.now();

// Nachrichtenanzahl pro Benutzer verfolgen
if (!rateLimitMap.has(userId)) {
rateLimitMap.set(userId, []);
}

const userMessages = rateLimitMap.get(userId);

// Nachrichten der letzten Minute filtern
const recentMessages = userMessages.filter(
timestamp => now - timestamp < 60000
);

// Limit von 10 pro Minute
if (recentMessages.length >= 10) {
socket.emit('error', 'Nachrichtensende-Limit ΓΌberschritten');
return;
}

recentMessages.push(now);
rateLimitMap.set(userId, recentMessages);

// Nachricht verarbeiten
handleMessage(data);
});
});

// ========== 5. Eingabevalidierung ==========

socket.on('message', (data) => {
// Typvalidierung
if (typeof data !== 'object') {
return socket.emit('error', 'Falsches Datenformat');
}

// Pflichtfeldvalidierung
if (!data.type || !data.content) {
return socket.emit('error', 'Pflichtfelder fehlen');
}

// LΓ€ngenvalidierung
if (data.content.length > 1000) {
return socket.emit('error', 'Nachricht zu lang');
}

// XSS-PrΓ€vention
const sanitizedContent = sanitizeHtml(data.content);

// Verarbeiten
broadcastMessage(sanitizedContent);
});

// ========== 6. Namespace und Room ==========

// Mit Namespace isolieren
const chatNamespace = io.of('/chat');
const adminNamespace = io.of('/admin');

chatNamespace.on('connection', (socket) => {
// Allgemeiner Chat
});

adminNamespace.use(authenticateAdmin);
adminNamespace.on('connection', (socket) => {
// Nur Administratoren
});

// Berechtigungskontrolle mit Room
socket.on('join-room', (roomId) => {
if (canAccessRoom(socket.userId, roomId)) {
socket.join(roomId);
} else {
socket.emit('error', 'Keine Raum-Zugriffsberechtigung');
}
});

// ========== 7. DDoS-PrΓ€vention ==========

// Anzahl der Verbindungen begrenzen
const MAX_CONNECTIONS_PER_IP = 5;
const connectionsByIP = new Map();

io.on('connection', (socket) => {
const ip = socket.handshake.address;

const count = connectionsByIP.get(ip) || 0;

if (count >= MAX_CONNECTIONS_PER_IP) {
socket.disconnect();
return;
}

connectionsByIP.set(ip, count + 1);

socket.on('disconnect', () => {
const newCount = connectionsByIP.get(ip) - 1;
if (newCount <= 0) {
connectionsByIP.delete(ip);
} else {
connectionsByIP.set(ip, newCount);
}
});
});

F5. Fehlerbehandlung?​

A:

// ========== Client-Fehlerbehandlung ==========

class WebSocketClient {
constructor(url) {
this.url = url;
this.ws = null;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 10;
this.reconnectDelay = 1000;
}

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

this.ws.onopen = () => {
console.log('Verbindung erfolgreich');
this.reconnectAttempts = 0;
this.onConnected();
};

this.ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
this.onMessage(data);
} catch (error) {
console.error('Nachricht-Parsing-Fehler:', error);
}
};

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

this.ws.onclose = (event) => {
console.log('Getrennt:', event.code, event.reason);

// Normale Trennung (1000)
if (event.code === 1000) {
console.log('Normale Trennung');
return;
}

// Neuverbindungsversuch
this.reconnect();
};
} catch (error) {
console.error('Verbindungsfehler:', error);
this.reconnect();
}
}

reconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error('Neuverbindung aufgegeben');
this.onReconnectFailed();
return;
}

this.reconnectAttempts++;
const delay = Math.min(
this.reconnectDelay * Math.pow(2, this.reconnectAttempts),
30000
);

console.log(`Neuverbindungsversuch in ${delay}ms (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);

setTimeout(() => {
this.connect();
}, delay);
}

send(data) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
try {
this.ws.send(JSON.stringify(data));
} catch (error) {
console.error('Sendefehler:', error);
}
} else {
console.error('WebSocket nicht geΓΆffnet');
// In Nachrichtenwarteschlange speichern
this.queueMessage(data);
}
}

close() {
if (this.ws) {
this.ws.close(1000, 'Client-Trennung');
}
}

// Event-Handler (ΓΌberschreiben)
onConnected() {}
onMessage(data) {}
onError(error) {}
onReconnectFailed() {}

queueMessage(data) {
// Offline-Nachrichtenwarteschlange implementieren
}
}

// Verwendung
const client = new WebSocketClient('wss://api.example.com');

client.onConnected = () => {
console.log('Verbunden!');
};

client.onMessage = (data) => {
console.log('Nachricht:', data);
};

client.onError = (error) => {
console.error('Fehler aufgetreten:', error);
// Fehler in UI anzeigen
showErrorNotification('Verbindungsfehler');
};

client.onReconnectFailed = () => {
// Neuverbindungsfehler behandeln
showErrorModal('Kann nicht mit Server verbinden');
};

client.connect();

// ========== Server-Fehlerbehandlung ==========

io.on('connection', (socket) => {
// Fehler-Handler
socket.on('error', (error) => {
console.error('Socket-Fehler:', error);
});

// Nachrichtenverarbeitungsfehler
socket.on('message', async (data) => {
try {
// Eingabevalidierung
if (!isValidMessage(data)) {
throw new Error('UngΓΌltige Nachricht');
}

// Verarbeiten
await processMessage(data);
} catch (error) {
console.error('Nachrichtenverarbeitungsfehler:', error);

// Fehler an Client senden
socket.emit('error', {
code: 'MESSAGE_PROCESSING_ERROR',
message: error.message
});
}
});

// Trennung behandeln
socket.on('disconnect', (reason) => {
console.log('Getrennt:', reason);

// Benutzer aufrΓ€umen
cleanupUser(socket.userId);

// Andere Benutzer benachrichtigen
socket.broadcast.emit('user-left', socket.userId);
});
});

// Globaler Fehler-Handler
io.engine.on('connection_error', (error) => {
console.error('Verbindungsfehler:', error);
});

// ========== Timeout-Behandlung ==========

socket.on('message', (data, callback) => {
// Timeout einstellen (5 Sekunden)
const timeout = setTimeout(() => {
callback({
error: 'TIMEOUT',
message: 'Antwortzeit ΓΌberschritten'
});
}, 5000);

// Verarbeiten
processMessage(data)
.then(result => {
clearTimeout(timeout);
callback({ success: true, data: result });
})
.catch(error => {
clearTimeout(timeout);
callback({ error: error.message });
});
});

πŸŽ“ NΓ€chste Schritte​

Sobald Sie Echtzeit-Kommunikation verstehen, kΓΆnnen Sie Folgendes lernen:

  1. HTTP-Grundlagen (Dokument in Vorbereitung) - HTTP-Protokoll verstehen
  2. Was ist eine API? (Dokument in Vorbereitung) - Grundlegendes API-Konzept
  3. REST API vs GraphQL - API-Design

Praktische Übungen​

# ========== 1. Chat-App mit Socket.io ==========

mkdir chat-app
cd chat-app
npm init -y
npm install express socket.io

# Nach dem Schreiben von server.js
node server.js

# ========== 2. Echtzeit-Benachrichtigungen mit SSE ==========

mkdir sse-demo
cd sse-demo
npm install express

# server.js schreiben
# index.html schreiben

node server.js
# Auf http://localhost:3000 zugreifen

# ========== 3. Spiel mit WebSocket ==========

mkdir websocket-game
cd websocket-game
npm install ws express

# Multiplayer-Spiel implementieren
node server.js

🎬 Fazit​

Echtzeit-Kommunikation ist eine Kerntechnologie im modernen Web:

  • Long Polling: Legacy-UnterstΓΌtzung, HTTP-basiert
  • SSE: Server-Push, einfache Implementierung
  • WebSocket: Bidirektional in Echtzeit, beste Leistung
  • Auswahlkriterien: Anforderungen, Umgebung, KomplexitΓ€t

WΓ€hlen Sie die richtige Echtzeit-Kommunikationsmethode fΓΌr Ihr Projekt und schaffen Sie eine großartige Benutzererfahrung! πŸ”΄