π΄ 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