主题
🔗 WebSocket 协议
HTTP 是"你问我才答",WebSocket 是"咱们开着麦聊"
WebSocket vs HTTP
| HTTP | WebSocket | |
|---|---|---|
| 通信模式 | 请求→响应,一问一答 | 全双工,双方随时发 |
| 连接 | 短连接/长连接(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 协议,不再是 HTTPWebSocket 数据帧
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 |
+---------------------------------------------------------------+| 字段 | 说明 |
|---|---|
| FIN | 1=最后一帧,0=还有后续帧 |
| opcode | 帧类型:1=文本,2=二进制,8=关闭,9=Ping,10=Pong |
| MASK | 1=数据已掩码(客户端→服务器必须掩码) |
| 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,不需要手动处理 Pingnginx 代理 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 长轮询
| WebSocket | SSE | 长轮询 | |
|---|---|---|---|
| 方向 | 双向 | 服务器→客户端 | 客户端→服务器→响应 |
| 协议 | ws:// / wss:// | HTTP | HTTP |
| 开销 | 极低 | 低 | 高(频繁重建连接) |
| 适用 | 聊天、游戏、协同编辑 | 股票行情、通知推送 | 兼容老浏览器 |
需要双向实时 → WebSocket;只需服务端推送 → SSE;兼容老系统 → 长轮询。
🎯 本章要点
- WebSocket 通过 HTTP Upgrade 握手后升级到全双工持久连接
- 帧比 HTTP 请求头小得多(2-14 字节 vs 几百字节)
- 心跳保活:每 30 秒 Ping/Pong,防止代理断开
- nginx 代理
proxy_read_timeout要配 3600s,否则空闲被掐 - 选型:双向实时选 WebSocket,单向推送选 SSE
加载练习题中...