何时用 WebSocket

需要服务器主动推送的场景:

  • 实时聊天 / 消息
  • 实时协作(多人编辑)
  • 实时数据(股票 / 监控)
  • 游戏 / 在线状态
  • 通知系统

HTTP 长轮询 / SSE(Server-Sent Events)是替代方案;WebSocket 双向 + 低延迟。

两个库

ws Socket.IO
协议 标准 WebSocket 自己协议(基于 WS + 长轮询 fallback)
体积
房间 / 命名空间 自己实现 内置
自动重连 自己写 内置
浏览器支持 现代 含 fallback
推荐 想直接控制 WebSocket 不想造轮子

ws:轻量原生

npm install ws
// server.js
import { WebSocketServer } from 'ws';

const wss = new WebSocketServer({ port: 8080 });

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

    ws.on('message', data => {
        console.log('Got:', data.toString());

        // 广播给所有连接
        wss.clients.forEach(client => {
            if (client.readyState === WebSocket.OPEN) {
                client.send(`echo: ${data}`);
            }
        });
    });

    ws.on('close', () => console.log('Client gone'));
    ws.on('error', err => console.error(err));

    ws.send('Welcome');
});

客户端(浏览器):

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

ws.onopen = () => ws.send('hello');
ws.onmessage = e => console.log('received:', e.data);
ws.onclose = () => console.log('disconnected');
ws.onerror = e => console.error(e);

心跳(保活)

WS 连接可能被 NAT / 代理在静默时关闭。加心跳:

function heartbeat() {
    this.isAlive = true;
}

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

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

wss.on('close', () => clearInterval(interval));

集成到 Express

import express from 'express';
import { WebSocketServer } from 'ws';
import { createServer } from 'http';

const app = express();
const server = createServer(app);
const wss = new WebSocketServer({ server });

wss.on('connection', ws => {
    ws.send('Welcome');
});

app.get('/', (req, res) => res.send('HTTP works too'));

server.listen(3000);

同一端口 HTTP + WebSocket 共存。

Socket.IO:开箱即用

npm install socket.io
import { Server } from 'socket.io';
import { createServer } from 'http';

const httpServer = createServer();
const io = new Server(httpServer, {
    cors: { origin: '*' },
});

io.on('connection', socket => {
    console.log('Client connected:', socket.id);

    socket.on('chat:message', msg => {
        io.emit('chat:message', { from: socket.id, msg });    // 广播
    });

    socket.on('join', room => {
        socket.join(room);
        io.to(room).emit('user:joined', socket.id);
    });
});

httpServer.listen(3000);

客户端:

import { io } from 'socket.io-client';

const socket = io('http://localhost:3000');
socket.emit('chat:message', 'hello');
socket.on('chat:message', data => console.log(data));

房间 / 命名空间(Socket.IO 强项)

// 命名空间(隔离不同业务)
const chat = io.of('/chat');
chat.on('connection', socket => { ... });

// 房间(按需聚合)
socket.join('room-42');
socket.to('room-42').emit('msg', 'hi room');
io.in('room-42').emit('msg', 'hi room');

横向扩展(多实例)

单机 WS 几万连接没问题——更多就要多机:

  • Socket.IO:用 @socket.io/redis-adapter 经 Redis 同步事件
  • ws:自己用 Redis Pub/Sub 同步

性能 / 内存

  • 每个连接占 KB 级内存(OS socket + Node 状态)
  • 单机能扛 10K-100K 连接(取决于消息频率)
  • 大规模考虑 sticky session(负载均衡器把同一用户路由到同一实例)

安全

  • TLS(wss://):跨公网必须
  • 鉴权:连接时验 JWT / cookie,未授权 close
  • 限流:单 IP 最多几个连接 + 消息频率限制
  • 消息大小:限制 max payload size(默认 ws 100 MB,太大)
const wss = new WebSocketServer({
    port: 8080,
    maxPayload: 64 * 1024,    // 64 KB
});

  • WS 没有自动重连(ws 库)—— 客户端必须自己写
  • 心跳是必须的——否则连接可能在你不知道时已经死了
  • Socket.IO 客户端和服务器版本必须一致——不然协议不兼容
  • 大量广播 = 大量 CPU——考虑用 Redis Pub/Sub 解耦

下一篇:PostgreSQL 客户端。