Skip to content

🔗 WebSocket 协议

HTTP 是"你问我才答",WebSocket 是"咱们开着麦聊"

WebSocket vs HTTP

HTTPWebSocket
通信模式请求→响应,一问一答全双工,双方随时发
连接短连接/长连接(Keep-Alive)持久连接
头部开销每次几百字节2-14 字节
典型场景网页、API聊天、实时推送、游戏

HTTP 是写信,WebSocket 是打电话。


WebSocket 握手

本质是通过 HTTP 升级到 WebSocket:

客户端请求(HTTP Upgrade):
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

服务器响应(101 Switching):
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

// 此后切换到 WebSocket 协议,不再是 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|  (4)  |A|     (7)     |           (16/64)             |
|N|V|V|V|       |S|             |                               |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+-------------------------------+
|     Masking-key (如果是客户端发给服务器)                         |
+---------------------------------------------------------------+
|                     Payload Data                              |
+---------------------------------------------------------------+
字段说明
FIN1=最后一帧,0=还有后续帧
opcode帧类型:1=文本,2=二进制,8=关闭,9=Ping,10=Pong
MASK1=数据已掩码(客户端→服务器必须掩码)
Payload实际数据

简单 WebSocket 服务(Python)

python
# pip install websockets
import asyncio
import websockets

connected = set()

async def handler(websocket):
    connected.add(websocket)
    try:
        async for message in websocket:
            # 广播给所有连接的客户端
            for conn in connected:
                if conn != websocket:
                    await conn.send(message)
    finally:
        connected.remove(conn)

async def main():
    async with websockets.serve(handler, "0.0.0.0", 8765):
        await asyncio.Future()  # 永远运行

asyncio.run(main())

WebSocket 心跳

长时间没消息,中间代理(nginx/负载均衡器)可能断开连接。心跳保活:

python
import asyncio
import websockets

async def handler(websocket):
    async def heartbeat():
        while True:
            try:
                await websocket.ping()
                await asyncio.sleep(30)  # 每 30 秒 ping 一次
            except:
                break

    # 同时收消息 + 发心跳
    await asyncio.gather(
        receive_messages(websocket),
        heartbeat(),
    )

async def receive_messages(websocket):
    async for message in websocket:
        await websocket.send(f"Echo: {message}")

浏览器端:

javascript
const ws = new WebSocket('wss://example.com/chat');

ws.onopen = () => console.log('已连接');
ws.onmessage = (e) => console.log('收到:', e.data);
ws.onclose = () => console.log('已断开');

// 发送
ws.send('Hello');

// 浏览器会自动回复 Pong,不需要手动处理 Ping

nginx 代理 WebSocket

nginx
server {
    location /ws/ {
        proxy_pass http://127.0.0.1:8765;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }
}

注意

proxy_read_timeout 必须设得够长,否则 WebSocket 空闲超过默认 60 秒就会被 nginx 断开。


WebSocket vs SSE vs 长轮询

WebSocketSSE长轮询
方向双向服务器→客户端客户端→服务器→响应
协议ws:// / wss://HTTPHTTP
开销极低高(频繁重建连接)
适用聊天、游戏、协同编辑股票行情、通知推送兼容老浏览器

需要双向实时 → WebSocket;只需服务端推送 → SSE;兼容老系统 → 长轮询。


🎯 本章要点

  • WebSocket 通过 HTTP Upgrade 握手后升级到全双工持久连接
  • 帧比 HTTP 请求头小得多(2-14 字节 vs 几百字节)
  • 心跳保活:每 30 秒 Ping/Pong,防止代理断开
  • nginx 代理 proxy_read_timeout 要配 3600s,否则空闲被掐
  • 选型:双向实时选 WebSocket,单向推送选 SSE
加载练习题中...

有问题或补充?欢迎留言