WebSocket / SSE / WebTransport
Три механизма real-time коммуникации браузера с сервером: WebSocket — полнодуплексный TCP-туннель поверх HTTP Upgrade; SSE — односторонний server-push по обычному HTTP; WebTransport — современный API поверх QUIC; выбор между ними — типичный design-вопрос на senior-собеседованиях.
// WebSocket: полнодуплексный канал, обязательно wss:// в productionconst ws = new WebSocket('wss://api.example.com/ws');
ws.addEventListener('open', () => { ws.send(JSON.stringify({ type: 'subscribe', channel: 'prices' }));});ws.addEventListener('message', ({ data }) => { const msg = JSON.parse(data); renderUpdate(msg);});ws.addEventListener('close', ({ code, reason, wasClean }) => { // 1000 = нормальное закрытие | 1001 = going away | 1006 = abnormal (network drop) if (!wasClean) scheduleReconnect();});ws.addEventListener('error', e => console.error('WS error', e));
// Exponential backoff reconnectfunction scheduleReconnect(attempt = 0) { const delay = Math.min(500 * 2 ** attempt, 30_000); // 500ms → 1s → 2s → ... → 30s setTimeout(() => connect(attempt + 1), delay + Math.random() * 500); // jitter}
// Heartbeat: обнаружение «мёртвых» соединений (NAT timeout ~30-60 сек)setInterval(() => { if (ws.readyState === WebSocket.OPEN) ws.send('{"type":"ping"}');}, 25_000);// SSE (Server-Sent Events): server -> client, автоматический reconnect браузеромconst es = new EventSource('/api/stream'); // поверх обычного HTTP
es.addEventListener('message', ({ data }) => { // дефолтное событие renderChunk(JSON.parse(data));});es.addEventListener('price-update', ({ data }) => { // именованное событие updateTicker(JSON.parse(data));});es.addEventListener('error', () => { // EventSource сам переподключается; readyState: CONNECTING | OPEN | CLOSED if (es.readyState === EventSource.CLOSED) setStatus('disconnected');});es.close(); // явная отписка
// Server (Node.js): формат text/event-stream// res.writeHead(200, {// 'Content-Type': 'text/event-stream',// 'Cache-Control': 'no-cache',// 'Connection': 'keep-alive',// 'X-Accel-Buffering': 'no', // отключить буферизацию nginx// });// res.write('data: {"price":42}\n\n'); // двойной \n = конец события// res.write('event: price-update\ndata: {"price":43}\n\n');// res.write('id: 123\n'); // Last-Event-ID для retry// WebTransport: QUIC-based, низкая латентность, мультиплексирование (Chrome 97+)const transport = new WebTransport('https://example.com:4433/wt');await transport.ready; // QUIC handshake
// Ненадёжные датаграммы (unreliable, как UDP): низкая латентность, без гарантийconst dgramWriter = transport.datagrams.writable.getWriter();await dgramWriter.write(new TextEncoder().encode('telemetry'));
// Надёжные потоки (reliable streams): отдельный QUIC stream, без head-of-line blockingconst stream = await transport.createBidirectionalStream();const writer = stream.writable.getWriter();await writer.write(new TextEncoder().encode('command'));
// SSE vs WebSocket vs WebTransport//// Направление SSE: server->client WS: duplex WT: duplex// Протокол HTTP/1.1+ TCP (WS) QUIC (UDP-based)// Auto-retry ✓ браузер ручной ручной// Head-of-line да (HTTP/1.1) да (TCP) нет (QUIC streams)// Поддержка все браузеры все браузеры Chrome 97+, Edge, Firefox 114+// CPU overhead низкий средний низкийИтог: SSE — простейший server-push с HTTP и авто-reconnect; WebSocket — полный дуплекс для интерактивности; WebTransport — low-latency QUIC для realtime-приложений нового поколения.