WebSocket 实时通信

2026-06-22 · 6 阅读 · 462字
API设计GoNode.js

WebSocket 实时通信

什么是 WebSocket

WebSocket 是一种在单个 TCP 连接上提供全双工通信的协议。它通过 HTTP 升级握手建立连接,之后客户端和服务端可以互相发送数据,无需轮询。

客户端                     服务端
  │                         │
  │── HTTP Upgrade Request ─→│
  │← HTTP 101 Switching ────│
  │                         │
  │← 双向消息 ──────────────→│
  │← 双向消息 ──────────────→│
  │                         │
  │── Close Frame ──────────→│

工作原理

握手阶段

客户端发起 HTTP Upgrade 请求:

GET /ws HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

服务端返回 101 Switching Protocols:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

数据帧格式

WebSocket 数据以帧(frame)为单位传输,每帧包含:

  • FIN:是否为最后一帧
  • Opcode:数据类型(text、binary、close、ping、pong)
  • Payload length:负载长度
  • Mask:客户端到服务端的帧需要掩码
  • Payload data:实际数据

Node.js 实现(ws 库)

服务端

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.send(JSON.stringify({
        type: 'system',
        message: 'Welcome to WebSocket server!'
    }));

    // 接收消息
    ws.on('message', (data) => {
        try {
            const msg = JSON.parse(data);
            console.log('Received:', msg);

            // 广播给所有客户端
            wss.clients.forEach((client) => {
                if (client.readyState === WebSocket.OPEN) {
                    client.send(JSON.stringify({
                        type: 'broadcast',
                        sender: msg.sender,
                        content: msg.content
                    }));
                }
            });
        } catch (err) {
            ws.send(JSON.stringify({ type: 'error', message: 'Invalid JSON' }));
        }
    });

    // 处理关闭
    ws.on('close', () => {
        console.log('Client disconnected');
    });

    // 心跳检测
    ws.isAlive = true;
    ws.on('pong', () => { ws.isAlive = true; });
});

// 心跳机制
const interval = setInterval(() => {
    wss.clients.forEach((ws) => {
        if (ws.isAlive === false) return ws.terminate();
        ws.isAlive = false;
        ws.ping();
    });
}, 30000);

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

客户端

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

ws.onopen = () => {
    console.log('Connected to server');
    ws.send(JSON.stringify({
        sender: 'Alice',
        content: 'Hello everyone!'
    }));
};

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

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

ws.onclose = () => {
    console.log('Connection closed');
};

Go 实现(gorilla/websocket)

服务端

import "github.com/gorilla/websocket"

var upgrader = websocket.Upgrader{
    CheckOrigin: func(r *http.Request) bool {
        return true // 生产环境需校验
    },
}

type Client struct {
    conn *websocket.Conn
    send chan []byte
}

type Hub struct {
    clients    map[*Client]bool
    broadcast  chan []byte
    register   chan *Client
    unregister chan *Client
}

func (h *Hub) Run() {
    for {
        select {
        case client := <-h.register:
            h.clients[client] = true
        case client := <-h.unregister:
            if _, ok := h.clients[client]; ok {
                delete(h.clients, client)
                close(client.send)
            }
        case message := <-h.broadcast:
            for client := range h.clients {
                select {
                case client.send <- message:
                default:
                    close(client.send)
                    delete(h.clients, client)
                }
            }
        }
    }
}

func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) {
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Println(err)
        return
    }
    client := &Client{conn: conn, send: make(chan []byte, 256)}
    hub.register <- client

    go client.writePump()
    go client.readPump(hub)
}

常见应用场景

  • 即时聊天:最经典的应用
  • 实时协作:在线文档、白板协作
  • 实时通知:订单状态变更、告警推送
  • 实时数据推送:行情、体育比分
  • 多人游戏:状态同步

注意事项

  • 使用 wss://(WebSocket Secure)加密连接
  • 实现心跳机制检测断连
  • 设置合理的超时和消息大小限制
  • 处理服务端优雅关闭
  • 考虑使用 Redis Pub/Sub 实现多节点广播