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 实现多节点广播