b1babo
timeline
keywords
articles
targets
projects
about
b1babo

2026 All Rights Reserved.

  • 关于本站
  • 所有文章
  • 站点地图
  • RSS Feed

Powered by Next.js & Trilium

  • Claude Code

WebSocket 详解与 SSE 对比

b1babo
2026年4月18日
2026年4月18日

WebSocket 详解与 SSE 对比

目录

  • WebSocket 基础
  • WebSocket 详解
  • WebSocket vs SSE 完整对比
  • 使用场景分析
  • 代码实现对比

WebSocket 基础

什么是 WebSocket

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。它于 2011 年被 IETF 标准化为 RFC 6455。

核心特点

特性说明
全双工通信客户端和服务器可以同时发送消息
持久连接建立连接后保持打开状态
低开销数据包头部小,减少传输开销
实时性消息可以立即推送,无需轮询
跨域支持支持跨域通信

基本原理

┌─────────────────────────────────────────────────────────────┐
│                    WebSocket 握手过程                         │
└─────────────────────────────────────────────────────────────┘

客户端                                      服务器
  │                                           │
  │  ───────── HTTP 握手请求 ──────────────>  │
  │  GET /ws HTTP/1.1                          │
  │  Host: server.com                          │
  │  Upgrade: websocket                        │
  │  Connection: Upgrade                       │
  │  Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==  │
  │  Sec-WebSocket-Version: 13                 │
  │                                           │
  │  <──────── HTTP 握手响应 ─────────────────  │
  │  HTTP/1.1 101 Switching Protocols          │
  │  Upgrade: websocket                        │
  │  Connection: Upgrade                       │
  │  Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=  │
  │                                           │
  │  ═════════ WebSocket 连接建立 ══════════  │
  │                                           │
  │  <────────── 二进制帧消息 ──────────────   │
  │  Frame 1: "Hello"                          │
  │                                           │
  │  ─────────── 二进制帧消息 ─────────────>   │
  │  Frame 2: "Hi there"                      │
  │                                           │
  │  <────────── 二进制帧消息 ──────────────   │
  │  Frame 3: "How are you?"                   │
  │                                           │
  │  ─────────── 二进制帧消息 ─────────────>   │
  │  Frame 4: {"status": "ok"}                │
  │                                           │
  │  ═════════════ 持续双向通信 ═════════════  │

WebSocket 详解

协议握手

客户端请求

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

服务器响应

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

握手关键点:

  • 状态码 101 表示协议切换
  • Sec-WebSocket-Accept 是对客户端 Key 的确认
  • 握手完成后,连接从 HTTP 切换到 WebSocket 协议

数据帧格式

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4bits)  |A|     (7bits)  |             (16/64 bits)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|     Extended payload length continued, if payload len == 127  |
+ - - - - - - - - - - - - - - - +-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
| Masking-key (continued)       |          Payload Data         |
+-------------------------------- - - - - - - - - - - - - - - - +
:                     Payload Data continued ...                :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|                     Payload Data continued ...                |
+---------------------------------------------------------------+

字段说明:

字段位说明
FIN1是否为最后一帧
RSV1-33保留位,通常为0
Opcode4操作码(0x1=文本, 0x2=二进制, 0x8=关闭)
MASK1是否使用掩码(客户端必须为1)
Payload Length7/7+16/7+64载荷长度
Masking Key32掩码密钥(客户端发送时必需)
Payload Data可变实际数据

Opcode 类型

Opcode名称说明
0x0Continuation继续帧
0x1TextUTF-8 文本帧
0x2Binary二进制帧
0x8Close关闭连接
0x9Ping心跳检测
0xAPong心跳响应

心跳机制

// 客户端发送 Ping
ws.ping();

// 服务器自动回复 Pong
// 或手动处理
ws.on('pong', () => {
    console.log('Received pong');
});

// 服务端实现(Node.js)
const interval = setInterval(() => {
    ws.ping();
}, 30000);

ws.on('pong', () => {
    clearInterval(interval);
});

连接状态

switch (ws.readyState) {
    case WebSocket.CONNECTING:  // 0 - 正在连接
        console.log('Connecting...');
        break;
    case WebSocket.OPEN:        // 1 - 连接已打开
        console.log('Connected');
        break;
    case WebSocket.CLOSING:     // 2 - 正在关闭
        console.log('Closing...');
        break;
    case WebSocket.CLOSED:      // 3 - 连接已关闭
        console.log('Closed');
        break;
}

WebSocket vs SSE 完整对比

核心差异

┌─────────────────────────────────────────────────────────────────────────┐
│                         WebSocket vs SSE 对比                           │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  WebSocket                    │  SSE                                   │
│  ─────────────────────────     │  ─────────────────────────             │
│                                                                         │
│  ████████████                  │  ██████████                            │
│  ██        ██  ←              │  ██        ██  ←                        │
│  ██  SERVER ██  ← 双向         │  ██  SERVER ██  ← 单向                │
│  ██        ██  →              │  ██        ██                           │
│  ████████████  →              │  ████████████                           │
│                                                                         │
│  • 全双工通信                   │  • 单向通信(服务器→客户端)           │
│  • 二进制帧格式                 │  • 文本格式                            │
│  • 需要协议升级                 │  • 基于HTTP                            │
│  • 手动实现重连                 │  • 自动重连                            │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

详细对比表

对比维度WebSocketSSE
通信方向双向(全双工)单向(服务器→客户端)
协议基础HTTP + WebSocket协议纯HTTP
连接方式协议升级(101状态码)持久HTTP连接
数据格式二进制帧文本(UTF-8)
浏览器APIWebSocketEventSource
重连机制需手动实现内置自动重连
二进制数据原生支持需Base64编码
断线续传不支持支持(通过Last-Event-ID)
实现复杂度中等简单
服务器负载较高(保持双向连接)较低
防火墙/代理可能有兼容性问题兼容性更好
适用场景聊天、游戏、协作推送、流式输出
性能开销较低(帧头部小)稍高(HTTP头)

性能对比

连接建立

WebSocket:
1. TCP 三次握手
2. HTTP 请求
3. 101 响应
= 约 2-3 RTT

SSE:
1. TCP 三次握手
2. HTTP 请求
3. 200 响应
= 约 2 RTT

差异:WebSocket 多一次协议升级往返

数据传输

WebSocket 帧:
+-----+------+-----+
| HEAD| MASK | DATA |
+-----+------+-----+
 2-14字节  4字节  变长
总开销:6-18字节

SSE 数据:
+-------+----------+
| data: | content  |\n\n
+-------+----------+
  6字节    变长    2字节
总开销:8+字节

结论:WebSocket 帧开销更小,适合高频小消息

兼容性对比

环境WebSocketSSE
现代浏览器✅ 完全支持✅ 完全支持
IE浏览器IE10+不支持
移动浏览器✅ 支持✅ 支持
Node.js✅ 原生支持✅ 可实现
Python✅ 多库支持✅ 可实现
代理服务器需配置一般兼容
防火墙可能被阻断通常通过

使用场景分析

WebSocket 最佳场景

1. 实时聊天应用

// 特点:双向实时通信
const ws = new WebSocket('wss://chat.example.com');

ws.send(JSON.stringify({
    type: 'message',
    content: 'Hello',
    userId: '123'
}));

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

为什么选 WebSocket:

  • 用户需要实时接收消息
  • 用户需要实时发送消息
  • 需要知道用户在线状态
  • 需要双向确认(已读回执)

2. 多人在线游戏

// 游戏状态同步
ws.send(JSON.stringify({
    type: 'move',
    x: player.x,
    y: player.y,
    timestamp: Date.now()
}));

// 实时接收其他玩家位置
ws.onmessage = (event) => {
    const gameData = JSON.parse(event.data);
    updateGameState(gameData);
};

为什么选 WebSocket:

  • 低延迟要求(毫秒级)
  • 高频率数据交换(每秒多次)
  • 双向通信(操作同步)
  • 二进制数据支持(游戏状态)

3. 实时协作编辑

// 操作同步(类似Google Docs)
ws.send(JSON.stringify({
    type: 'edit',
    position: {line: 5, ch: 10},
    content: 'added text',
    version: documentVersion
}));

// 接收其他人的编辑
ws.onmessage = (event) => {
    const edit = JSON.parse(event.data);
    applyEdit(edit);
};

为什么选 WebSocket:

  • 多人同时编辑
  • 操作需要立即同步
  • 冲突解决需要协商
  • 需要版本控制机制

4. 实时交易系统

// 买卖订单
ws.send(JSON.stringify({
    type: 'order',
    action: 'buy',
    price: 100.50,
    quantity: 10
}));

// 实时行情
ws.onmessage = (event) => {
    const quote = JSON.parse(event.data);
    updatePrice(quote);
};

为什么选 WebSocket:

  • 价格实时更新
  • 订单需要即时确认
  • 延迟直接影响收益
  • 需要可靠的双向通信

SSE 最佳场景

1. 大模型流式输出

async def stream_llm_response():
    async for token in llm.generate(prompt):
        yield f"data: {json.dumps({'token': token})}\n\n"
    yield "data: [DONE]\n\n"

为什么选 SSE:

  • 单向数据流(服务器→客户端)
  • 不需要客户端频繁发送数据
  • 实现简单,代码清晰
  • 与HTTP生态兼容

2. 服务器推送通知

const eventSource = new EventSource('/notifications');

eventSource.onmessage = (event) => {
    const notification = JSON.parse(event.data);
    showNotification(notification);
};

为什么选 SSE:

  • 只需要推送通知
  • 客户端不需要回复
  • 自动重连机制
  • 实现比WebSocket简单

3. 实时数据更新(股票、比分)

const scores = new EventSource('/live-scores');

scores.onmessage = (event) => {
    const score = JSON.parse(event.data);
    updateScoreBoard(score);
};

为什么选 SSE:

  • 单向数据推送
  • 不需要客户端确认
  • 可以缓存在代理服务器
  • 自动重连保证可靠性

4. 日志流、监控数据

const logs = new EventSource('/logs/stream');

logs.addEventListener('error', (e) => {
    console.error('Log error:', e.data);
});

logs.addEventListener('warning', (e) => {
    console.warn('Log warning:', e.data);
});

为什么选 SSE:

  • 大量单向日志数据
  • 支持事件类型分类
  • 可以断线续传
  • 文本格式便于阅读

场景选择决策树

┌─────────────────────────────────────────────────────────────────┐
│                    选择 WebSocket 或 SSE                         │
└─────────────────────────────────────────────────────────────────┘

需要客户端频繁发送数据?
│
├─ 是 → 使用 WebSocket
│        (聊天、游戏、协作编辑)
│
└─ 否 → 服务器单向推送?
         │
         ├─ 是 → 使用 SSE
         │        (通知、日志、流式输出)
         │
         └─ 否 → 考虑其他方案
                  (轮询、长轮询)


数据传输频率?
│
├─ 高频(每秒多次)→ WebSocket
│                      (游戏、实时交易)
│
└─ 低频(每秒几次)→ SSE 或 轮询
                          (通知、更新)


需要二进制数据?
│
├─ 是 → WebSocket
│
└─ 否 → SSE 或 WebSocket 均可


浏览器兼容性要求?
│
├─ 需要支持旧IE → WebSocket(IE10+)
│
└─ 现代浏览器 → SSE 更简单

代码实现对比

FastAPI 实现

WebSocket 实现

from fastapi import FastAPI, WebSocket
from fastapi.responses import HTMLResponse
import json

app = FastAPI()


@app.get("/")
async def get_client():
    return HTMLResponse("""
    <!DOCTYPE html>
    <html>
    <head>
        <title>WebSocket Chat</title>
    </head>
    <body>
        <h1>WebSocket 聊天室</h1>
        <input id="message" type="text" placeholder="输入消息">
        <button onclick="send()">发送</button>
        <div id="output"></div>

        <script>
            const ws = new WebSocket('ws://localhost:8000/ws');
            const output = document.getElementById('output');

            ws.onopen = () => {
                output.innerHTML += '<p>连接已建立</p>';
            };

            ws.onmessage = (event) => {
                const data = JSON.parse(event.data);
                output.innerHTML += `<p>${data.type}: ${data.content}</p>`;
            };

            ws.onerror = (error) => {
                output.innerHTML += '<p>连接错误</p>';
            };

            ws.onclose = () => {
                output.innerHTML += '<p>连接已关闭</p>';
            };

            function send() {
                const input = document.getElementById('message');
                ws.send(JSON.stringify({
                    type: 'message',
                    content: input.value
                }));
                input.value = '';
            }
        </script>
    </body>
    </html>
    """)


@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    try:
        while True:
            # 接收客户端消息
            data = await websocket.receive_text()
            message = json.loads(data)

            # 处理消息
            response = {
                "type": "echo",
                "content": f"收到: {message['content']}"
            }

            # 发送响应
            await websocket.send_json(response)

    except Exception as e:
        print(f"Error: {e}")
    finally:
        await websocket.close()


if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

SSE 实现

from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse, HTMLResponse
import asyncio
import json

app = FastAPI()


@app.get("/")
async def get_client():
    return HTMLResponse("""
    <!DOCTYPE html>
    <html>
    <head>
        <title>SSE Events</title>
    </head>
    <body>
        <h1>SSE 事件流</h1>
        <button onclick="startStream()">开始接收</button>
        <button onclick="stopStream()">停止接收</button>
        <div id="output"></div>

        <script>
            let eventSource = null;

            function startStream() {
                eventSource = new EventSource('/stream');

                eventSource.onmessage = (event) => {
                    if (event.data === '[DONE]') {
                        stopStream();
                        return;
                    }
                    const data = JSON.parse(event.data);
                    const output = document.getElementById('output');
                    output.innerHTML += `<p>${data.content}</p>`;
                };

                eventSource.onerror = (error) => {
                    console.error('SSE Error:', error);
                };
            }

            function stopStream() {
                if (eventSource) {
                    eventSource.close();
                    eventSource = null;
                }
            }
        </script>
    </body>
    </html>
    """)


async def generate_events():
    """生成SSE事件流"""
    messages = [
        "欢迎使用 SSE",
        "这是第一条消息",
        "这是第二条消息",
        "这是第三条消息",
        "流式输出结束"
    ]

    for i, msg in enumerate(messages):
        data = {
            "id": i,
            "content": msg,
            "timestamp": asyncio.get_event_loop().time()
        }
        yield f"data: {json.dumps(data)}\n\n"
        await asyncio.sleep(1)

    # 发送结束标记
    yield "data: [DONE]\n\n"


@app.get("/stream")
async def stream_events():
    return StreamingResponse(
        generate_events(),
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "X-Accel-Buffering": "no",
        }
    )


if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

Python 客户端对比

WebSocket 客户端

import asyncio
import websockets
import json

async def websocket_client():
    uri = "ws://localhost:8000/ws"

    async with websockets.connect(uri) as websocket:
        # 发送消息
        message = {"type": "message", "content": "Hello Server"}
        await websocket.send(json.dumps(message))

        # 接收响应
        while True:
            response = await websocket.recv()
            data = json.loads(response)
            print(f"收到: {data}")

            # 可以继续发送
            await asyncio.sleep(2)
            await websocket.send(json.dumps({
                "type": "message",
                "content": "Keep alive"
            }))


# 运行客户端
# asyncio.run(websocket_client())

SSE 客户端

import asyncio
import aiohttp
import json

async def sse_client():
    url = "http://localhost:8000/stream"

    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            async for line in response.content:
                line = line.decode('utf-8').strip()

                if not line:
                    continue

                if line.startswith('data: '):
                    data = line[6:]  # 去掉 "data: " 前缀

                    if data == '[DONE]':
                        print("流结束")
                        break

                    try:
                        event = json.loads(data)
                        print(f"收到事件: {event}")
                    except json.JSONDecodeError:
                        print(f"原始数据: {data}")


# 运行客户端
# asyncio.run(sse_client())

Node.js 实现对比

WebSocket 服务器

const WebSocket = require('ws');
const http = require('http');

const server = http.createServer();
const wss = new WebSocket.Server({ server });

wss.on('connection', (ws) => {
    console.log('客户端已连接');

    // 发送欢迎消息
    ws.send(JSON.stringify({
        type: 'welcome',
        content: '连接成功'
    }));

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

        // 广播给所有客户端
        wss.clients.forEach((client) => {
            if (client.readyState === WebSocket.OPEN) {
                client.send(JSON.stringify({
                    type: 'broadcast',
                    content: data.content
                }));
            }
        });
    });

    // 处理断开
    ws.on('close', () => {
        console.log('客户端已断开');
    });
});

server.listen(8080, () => {
    console.log('WebSocket 服务器运行在端口 8080');
});

SSE 服务器

const http = require('http');

const clients = new Set();

const server = http.createServer((req, res) => {
    if (req.url === '/stream') {
        // 设置 SSE 响应头
        res.writeHead(200, {
            'Content-Type': 'text/event-stream',
            'Cache-Control': 'no-cache',
            'Connection': 'keep-alive',
            'X-Accel-Buffering': 'no'
        });

        // 添加到客户端集合
        clients.add(res);

        // 发送初始消息
        res.write(`data: ${JSON.stringify({type: 'connected', content: '已连接'})}\n\n`);

        // 处理客户端断开
        req.on('close', () => {
            clients.delete(res);
            console.log('客户端断开连接');
        });
    } else {
        // 返回简单的 HTML 客户端
        res.writeHead(200, { 'Content-Type': 'text/html' });
        res.end(`
            <!DOCTYPE html>
            <html>
            <head><title>SSE Demo</title></head>
            <body>
                <h1>SSE 示例</h1>
                <div id="output"></div>
                <script>
                    const eventSource = new EventSource('/stream');
                    eventSource.onmessage = (event) => {
                        const data = JSON.parse(event.data);
                        document.getElementById('output').innerHTML +=
                            '<p>' + data.content + '</p>';
                    };
                </script>
            </body>
            </html>
        `);
    }
});

// 定时广播消息给所有客户端
setInterval(() => {
    const message = {
        type: 'update',
        content: `服务器时间: ${new Date().toLocaleTimeString()}`
    };

    clients.forEach((res) => {
        res.write(`data: ${JSON.stringify(message)}\n\n`);
    });
}, 1000);

server.listen(8080, () => {
    console.log('SSE 服务器运行在端口 8080');
});

实现复杂度对比

功能WebSocketSSE
服务端实现中等复杂度简单
客户端实现中等复杂度简单(浏览器)
连接管理需要手动管理自动处理
重连机制需要手动实现内置
心跳检测需要 Ping/Pong无标准机制
消息确认可实现不支持
代码行数较多较少

总结

选择建议

┌───────────────────────────────────────────────────────────────┐
│                      快速选择指南                              │
├───────────────────────────────────────────────────────────────┤
│                                                               │
│  场景                    │  推荐  │  原因                     │
│  ────────────────────────┼────────┼───────────────────────── │
│  聊天应用                │ WebSocket │ 双向实时通信           │
│  多人游戏                │ WebSocket │ 低延迟、高频交互        │
│  协作编辑                │ WebSocket │ 复杂状态同步           │
│  实时交易                │ WebSocket │ 毫秒级响应要求         │
│  LLM流式输出             │ SSE       │ 单向数据流              │
│  服务器通知              │ SSE       │ 推送即可                │
│  日志/监控数据           │ SSE       │ 大量单向数据            │
│  实时比分/股价           │ SSE       │ 单向更新                │
│                                                               │
└───────────────────────────────────────────────────────────────┘

技术选型决策因素

  1. 通信方向
    • 需要双向 → WebSocket
    • 单向即可 → SSE 更简单
  2. 实时性要求
    • 毫秒级 → WebSocket
    • 秒级 → SSE 足够
  3. 实现复杂度
    • 团队经验丰富 → 两者皆可
    • 快速开发 → SSE 更简单
  4. 浏览器兼容
    • 需要支持旧浏览器 → WebSocket
    • 现代浏览器 → SSE 更优雅
  5. 运维考虑
    • 代理/防火墙环境 → SSE 兼容性更好
    • 内网环境 → WebSocket 性能更好

混合使用

在复杂应用中,可以混合使用两者:

// 使用 WebSocket 处理双向通信
const ws = new WebSocket('wss://api.example.com/ws');

// 使用 SSE 处理单向推送
const notifications = new EventSource('https://api.example.com/notifications');

// 各司其职,发挥各自优势

WebSocket 和 SSE 各有优势,根据具体需求选择合适的技术方案是关键。

本页目录

  • WebSocket 详解与 SSE 对比
  • 目录
  • WebSocket 基础
    • 什么是 WebSocket
    • 核心特点
    • 基本原理
  • WebSocket 详解
    • 协议握手
      • 客户端请求
      • 服务器响应
    • 数据帧格式
    • Opcode 类型
    • 心跳机制
    • 连接状态
  • WebSocket vs SSE 完整对比
    • 核心差异
    • 详细对比表
    • 性能对比
      • 连接建立
      • 数据传输
    • 兼容性对比
  • 使用场景分析
    • WebSocket 最佳场景
      • 1. 实时聊天应用
      • 2. 多人在线游戏
      • 3. 实时协作编辑
      • 4. 实时交易系统
    • SSE 最佳场景
      • 1. 大模型流式输出
      • 2. 服务器推送通知
      • 3. 实时数据更新(股票、比分)
      • 4. 日志流、监控数据
    • 场景选择决策树
  • 代码实现对比
    • FastAPI 实现
      • WebSocket 实现
      • SSE 实现
    • Python 客户端对比
      • WebSocket 客户端
      • SSE 客户端
    • Node.js 实现对比
      • WebSocket 服务器
      • SSE 服务器
    • 实现复杂度对比
  • 总结
    • 选择建议
    • 技术选型决策因素
    • 混合使用

评论