正在加载,请稍候…

WebSocket vs SSE vs Long Polling: When to Use Each

Compare WebSocket, Server-Sent Events, and long polling for real-time web apps. Understand latency, browser support, server load, and which to choose.

The Real-Time Communication Problem

When you need a web app to receive updates from the server — chat messages, live scores, stock prices, notifications — you have three main options: WebSocket, Server-Sent Events (SSE), and long polling. They differ fundamentally in how the connection works, what they cost, and when they fail.

Choosing the wrong one leads to wasted resources, scaling headaches, or poor user experience. This guide explains exactly how each works and gives clear criteria for choosing.

Long Polling

How It Works

Long polling is the oldest technique and requires no special browser APIs.

  1. Client sends a regular HTTP request
  2. Server holds the connection open (doesn't respond immediately)
  3. When new data is available, the server responds
  4. Client immediately sends another request
  5. Repeat
// Client
async function longPoll() {
  while (true) {
    try {
      const res = await fetch('/api/events?lastId=' + lastEventId);
      const data = await res.json();
      processEvent(data);
      lastEventId = data.id;
    } catch (err) {
      // Connection dropped, wait before retry
      await new Promise(r => setTimeout(r, 2000));
    }
  }
}

longPoll();
// Server (Node.js / Express)
app.get('/api/events', async (req, res) => {
  const lastId = req.query.lastId;

  // Wait for up to 30 seconds for new data
  const data = await waitForNewData(lastId, { timeout: 30000 });

  if (data) {
    res.json(data);
  } else {
    res.status(204).end();  // timeout, no new data
  }
});

Pros and Cons

Pros:

  • Works everywhere — any HTTP server, any client
  • Passes through firewalls and proxies transparently
  • Easy to implement with existing infrastructure
  • Works with standard load balancers without sticky sessions

Cons:

  • HTTP overhead on every request (headers, TCP handshake overhead in HTTP/1.1)
  • Higher latency — minimum of one round-trip per event
  • Server holds connections open, consuming threads/memory
  • Not truly bidirectional — the client must always initiate

Best for: Environments with strict infrastructure constraints, simple notification systems where updates are infrequent, or legacy systems.

Server-Sent Events (SSE)

How It Works

SSE uses a single, persistent HTTP connection that the server can push data through at any time.

  1. Client opens one HTTP connection with EventSource
  2. Server streams text/event-stream data
  3. Connection stays open; server sends events whenever they occur
  4. Browser automatically reconnects if the connection drops
// Client — just 2 lines
const es = new EventSource('/api/stream');

es.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log('Received:', data);
};

// Named event types
es.addEventListener('user-joined', (event) => {
  console.log('User joined:', event.data);
});

es.addEventListener('error', () => {
  if (es.readyState === EventSource.CLOSED) {
    console.log('Connection closed');
  }
});
// Server (Node.js / Express)
app.get('/api/stream', (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  // Send a comment to keep the connection alive
  const keepAlive = setInterval(() => {
    res.write(': ping\n\n');
  }, 20000);

  // Send data
  const sendEvent = (type, data) => {
    res.write(`event: ${type}\n`);
    res.write(`data: ${JSON.stringify(data)}\n`);
    res.write(`id: ${Date.now()}\n\n`);  // id for reconnect recovery
  };

  // Subscribe to your event bus
  const unsubscribe = eventBus.on('update', (data) => sendEvent('update', data));

  req.on('close', () => {
    clearInterval(keepAlive);
    unsubscribe();
  });
});

Pros and Cons

Pros:

  • Native browser reconnection with Last-Event-ID header
  • Simpler than WebSocket — plain HTTP, works with standard infrastructure
  • Excellent browser support (all modern browsers)
  • Events can have types, IDs, and retry intervals
  • Works over HTTP/2 with multiplexing (many streams per connection)

Cons:

  • Unidirectional — server to client only; client sends separate requests
  • Limited to 6 concurrent connections per domain in HTTP/1.1 (HTTP/2 removes this limit)
  • Text-only (UTF-8); can't stream binary data efficiently
  • Some corporate proxies buffer responses and break streaming

Best for: Notifications, live feeds, dashboards, real-time logs — any scenario where data flows server → client and the client sends occasional updates via regular POST requests.

WebSocket

How It Works

WebSocket starts as an HTTP connection, then upgrades to a full-duplex TCP connection via the Upgrade handshake.

  1. Client sends HTTP Upgrade request
  2. Server responds with 101 Switching Protocols
  3. Both ends can now send messages at any time, in either direction
  4. Messages can be text or binary (ArrayBuffer / Blob)
// Client
const ws = new WebSocket('wss://api.example.com/ws');

ws.onopen = () => {
  console.log('Connected');
  ws.send(JSON.stringify({ type: 'subscribe', channel: 'prices' }));
};

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  handleMessage(msg);
};

ws.onclose = (event) => {
  console.log('Closed:', event.code, event.reason);
  // Implement exponential backoff reconnect
  setTimeout(reconnect, Math.min(30000, 1000 * 2 ** retryCount++));
};

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

// Send from client
function sendMessage(data) {
  if (ws.readyState === WebSocket.OPEN) {
    ws.send(JSON.stringify(data));
  }
}
// Server (Node.js / ws library)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws, req) => {
  console.log('Client connected from', req.socket.remoteAddress);

  ws.on('message', (message) => {
    const data = JSON.parse(message);
    // handle data.type === 'subscribe', 'unsubscribe', 'message', etc.
    handleClientMessage(ws, data);
  });

  ws.on('close', (code, reason) => {
    console.log('Client disconnected:', code, reason.toString());
    cleanupClient(ws);
  });

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

  // Ping to detect stale connections
  ws.isAlive = true;
  ws.on('pong', () => { ws.isAlive = true; });
});

// Heartbeat — terminate broken connections
setInterval(() => {
  wss.clients.forEach(ws => {
    if (!ws.isAlive) return ws.terminate();
    ws.isAlive = false;
    ws.ping();
  });
}, 30000);

Pros and Cons

Pros:

  • True bidirectional communication — client and server both push at any time
  • Low overhead per message after the initial handshake (2–10 byte frame header vs hundreds of bytes for HTTP)
  • Supports binary data (images, audio, file chunks)
  • Very low latency for high-frequency messaging (gaming, trading)

Cons:

  • Requires WebSocket support in infrastructure (load balancers, proxies must be configured)
  • No automatic reconnection — you must implement it yourself
  • Stateful connections complicate horizontal scaling (sticky sessions or pub/sub layer needed)
  • Firewall/proxy issues in restrictive corporate networks

Best for: Chat, multiplayer games, live collaboration (Google Docs-style), financial trading dashboards, any app where the client also sends frequent messages.

Side-by-Side Comparison

Feature Long Polling SSE WebSocket
Direction Client → Server → Client Server → Client Bidirectional
Protocol HTTP HTTP WebSocket (TCP)
Binary data Via encoding No Yes
Auto-reconnect Manual Built-in Manual
Browser support Universal All modern All modern
HTTP/2 support Yes Multiplexed N/A
Proxy/firewall friendly ✅ Best ✅ Good ⚠️ Configure needed
Message overhead High (full HTTP) Low Minimal
Scaling complexity Low Low-Medium High

Decision Guide

Use Long Polling if:

  • You need maximum infrastructure compatibility
  • Updates are infrequent (< once per 30 seconds)
  • You're behind strict proxies or older load balancers
  • You need a quick implementation without new infrastructure

Use SSE if:

  • Data flows primarily server → client
  • You want simplicity with native browser reconnection
  • You're building notifications, live feeds, progress updates
  • You're using HTTP/2 (SSE becomes very efficient)

Use WebSocket if:

  • You need true bidirectional messaging (chat, games, collaboration)
  • You're sending binary data
  • Message frequency is high (> a few per second)
  • Latency is critical (trading, gaming)

Scaling Considerations

Long polling and SSE are easier to scale because they use standard HTTP — any stateless load balancer works. WebSocket connections are stateful, so a load balancer must route the same client to the same server (sticky sessions), or you need a pub/sub layer (Redis, NATS) so any server can publish to any connected client.

For high-scale WebSocket deployments, look at: Socket.IO (handles reconnection, rooms), Ably, Pusher, or managed WebSocket services.

→ Use the URL Parser to inspect WebSocket endpoint URLs and their query parameters.