WebSockets、Server-Sent Events 與輪詢:何時選用哪種方式
WebSockets vs Server-Sent Events vs Polling: When to Use Which
WebSockets、Server-Sent Events(SSE)與輪詢各有適用情境,本文按通訊方向、延遲、重連、代理相容性及複雜度比較三種方案。輪詢適合更新間隔較長或基礎設施簡單的情況,SSE 適合單向推送,WebSockets 則適合低延遲雙向通訊或高頻資料交換。多數網頁應用的即時需求可由 SSE 覆蓋 80%,WebSockets 則適用於確有雙向通訊及低延遲要求的場景。
Real-time features — chat, live notifications, collaborative editing, live dashboards — all require the server to push data to the client. There are three main approaches: WebSockets, Server-Sent Events (SSE), and polling. Picking the wrong one leads to either unnecessary complexity or something that doesn't scale.
Polling
The simplest approach: the client asks the server for updates on a schedule.
// Client
setInterval(async () => {
const response = await fetch('/api/notifications/new?since=' + lastCheck);
const data = await response.json();
if (data.notifications.length > 0) {
updateUI(data.notifications);
lastCheck = Date.now();
}
}, 3000); // check every 3 seconds
Pros:
- Works with any HTTP server
- Simple to implement and debug
- No special infrastructure
- Works behind all proxies and firewalls
Cons:
- Latency = polling interval (up to 3 seconds in the example above)
- Wasteful: most polls return nothing
- Generates constant load even when idle
Long polling reduces waste by holding the connection open until data is available:
async function longPoll() {
while (true) {
try {
const response = await fetch('/api/events/wait'); // server holds 30s
const data = await response.json();
handleEvent(data);
} catch {
await new Promise(r => setTimeout(r, 5000)); // retry after error
}
}
}
// Server (Express)
app.get('/api/events/wait', async (req, res) => {
const event = await waitForEvent(req.user.id, { timeout: 30000 });
if (event) {
res.json(event);
} else {
res.json({ type: 'timeout' });
}
});
Long polling is a reasonable choice when you need real-time but can't use WebSockets (corporate proxies, simple infrastructure).
Server-Sent Events (SSE)
SSE is a simple protocol for one-way server-to-client streaming over HTTP. The browser keeps a persistent HTTP connection open; the server sends events as text:
// Client
const source = new EventSource('/api/stream');
source.addEventListener('message', (e) => {
const data = JSON.parse(e.data);
updateUI(data);
});
source.addEventListener('notification', (e) => {
showNotification(JSON.parse(e.data));
});
source.addEventListener('error', () => {
console.log('Connection lost, will auto-reconnect');
});
// EventSource automatically reconnects on disconnect
source.close(); // explicit close
// Server (Express)
app.get('/api/stream', (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'X-Accel-Buffering': 'no' // disable nginx buffering
});
const send = (event, data) => {
res.write(`event: ${event}\n`);
res.write(`data: ${JSON.stringify(data)}\n\n`);
};
// Send initial data
send('message', { type: 'connected', userId: req.user.id });
// Subscribe to updates
const unsubscribe = notificationBus.subscribe(req.user.id, (event) => {
send('notification', event);
});
// Cleanup on disconnect
req.on('close', () => {
unsubscribe();
});
});
Pros:
- Simpler than WebSockets (standard HTTP, easy to debug in Network panel)
- Automatic reconnection built into the browser
- Works with HTTP/2 multiplexing
- Works behind most proxies (it's just HTTP)
Cons:
- One-way only (server → client)
- HTTP/1.1 limited to 6 connections per domain (HTTP/2 removes this)
- Not supported in older IE (but 97%+ browser support now)
Best for: Notifications, live feeds, progress updates, AI streaming responses (ChatGPT-style).
WebSockets
WebSockets provide full-duplex, persistent connections between client and server. Both sides can send messages at any time.
// Client
const ws = new WebSocket('wss://api.example.com/ws');
ws.addEventListener('open', () => {
ws.send(JSON.stringify({ type: 'subscribe', channel: 'room-42' }));
});
ws.addEventListener('message', (e) => {
const data = JSON.parse(e.data);
handleMessage(data);
});
ws.addEventListener('close', (e) => {
console.log('Closed:', e.code, e.reason);
// Implement reconnection logic
});
ws.addEventListener('error', () => {
console.error('WebSocket error');
});
// Send a message
ws.send(JSON.stringify({ type: 'chat', text: 'Hello!' }));
// Server (Node.js with 'ws' library)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
const rooms = new Map();
wss.on('connection', (ws, req) => {
ws.userId = authenticate(req); // extract from cookie/token
let currentRoom = null;
ws.on('message', (message) => {
const data = JSON.parse(message);
if (data.type === 'subscribe') {
currentRoom = data.channel;
if (!rooms.has(currentRoom)) rooms.set(currentRoom, new Set());
rooms.get(currentRoom).add(ws);
}
if (data.type === 'chat') {
const room = rooms.get(currentRoom);
if (room) {
room.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({
type: 'chat',
userId: ws.userId,
text: data.text
}));
}
});
}
}
});
ws.on('close', () => {
if (currentRoom && rooms.has(currentRoom)) {
rooms.get(currentRoom).delete(ws);
}
});
});
Pros:
- Full duplex (both sides can send)
- Low latency (no HTTP overhead per message)
- Ideal for high-frequency bidirectional communication
Cons:
- More complex than SSE
- Some proxies/firewalls block WebSocket upgrades
- Stateful connection — harder to load balance
- Reconnection logic must be implemented manually
Libraries that simplify WebSocket complexity:
// Socket.io handles reconnection, rooms, namespaces, fallbacks
const io = require('socket.io')(server);
io.on('connection', (socket) => {
socket.on('join-room', (room) => {
socket.join(room);
});
socket.on('send-message', ({ room, text }) => {
io.to(room).emit('message', { text, from: socket.id });
});
});
Decision Guide
| Polling | SSE | WebSockets | |
|---|---|---|---|
| Direction | Client → Server (periodically) | Server → Client | Both |
| Protocol | HTTP | HTTP | WS/WSS |
| Reconnect | Manual | Automatic | Manual |
| Proxy friendly | Yes | Usually | Sometimes |
| Complexity | Low | Medium | High |
| Load balancing | Easy | Easy | Needs sticky sessions |
Use polling when:
- Infrequent updates (> 30 second intervals acceptable)
- Simple infrastructure, can't add WebSocket support
- Low traffic, simplicity matters more than efficiency
Use SSE when:
- Server pushes data to many clients (notifications, feeds, live updates)
- One-way communication is sufficient
- You want simplicity with better performance than polling
- AI text streaming (ChatGPT, Claude streaming responses use SSE)
Use WebSockets when:
- Bidirectional, low-latency communication (chat, collaborative editing, games)
- High-frequency data exchange (trading, live sports, multiplayer)
- Client needs to send data frequently, not just receive
For most web apps, SSE covers 80% of real-time needs at much lower complexity than WebSockets. Reserve WebSockets for genuinely bidirectional, latency-sensitive use cases.
來源:DEV Community · dev.to