WebSocket
约 971 字大约 3 分钟
WebSocket实时通信
2024-08-13
概述
WebSocket 是一种在单个 TCP 连接上提供全双工通信的协议,常用于实时应用如聊天、游戏、实时协作等。
与 HTTP 对比
| 特性 | HTTP | WebSocket |
|---|---|---|
| 连接方式 | 请求-响应 | 持久连接 |
| 通信方向 | 客户端发起 | 双向通信 |
| 服务器推送 | 需轮询/SSE | 原生支持 |
| 数据格式 | 文本/二进制 | 文本/二进制 |
| 断开连接 | 每次请求后 | 保持连接 |
基础用法
客户端
// 创建连接
const ws = new WebSocket('ws://localhost:8080');
// 连接打开
ws.onopen = () => {
console.log('Connected to server');
ws.send('Hello Server!');
};
// 接收消息
ws.onmessage = (event) => {
console.log('Message from server:', event.data);
// event.data 可以是字符串或 Blob/ArrayBuffer
};
// 连接错误
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
// 连接关闭
ws.onclose = (event) => {
console.log('Connection closed', event.code, event.reason);
};
// 发送消息
ws.send(JSON.stringify({ type: 'chat', content: 'Hello!' }));
// 关闭连接
ws.close(1000, 'Normal closure');服务端 (Node.js)
const { WebSocketServer } = require('ws');
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (ws, req) => {
const clientIP = req.socket.remoteAddress;
console.log(`Client connected: ${clientIP}`);
// 广播消息给所有客户端
wss.clients.forEach(client => {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(`User joined: ${clientIP}`);
}
});
// 接收消息
ws.on('message', (message) => {
console.log('Received:', message.toString());
// 广播给所有客户端
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(message.toString());
}
});
});
// 发送消息
ws.send('Welcome to the chat!');
// 处理关闭
ws.on('close', () => {
console.log('Client disconnected');
});
});心跳机制
const HEARTBEAT_INTERVAL = 30000;
const HEARTBEAT_TIMEOUT = 60000;
let heartbeatTimer;
let isAlive = true;
function startHeartbeat() {
clearInterval(heartbeatTimer);
heartbeatTimer = setInterval(() => {
if (!isAlive) {
console.log('Connection lost, reconnecting...');
ws.close();
return;
}
isAlive = false;
ws.send(JSON.stringify({ type: 'ping' }));
}, HEARTBEAT_INTERVAL);
}
ws.onopen = () => startHeartbeat();
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'pong') {
isAlive = true;
}
};
ws.onclose = () => clearInterval(heartbeatTimer);重连机制
class WebSocketClient {
constructor(url, options = {}) {
this.url = url;
this.reconnectInterval = options.reconnectInterval || 5000;
this.maxReconnectAttempts = options.maxReconnectAttempts || 10;
this.reconnectAttempts = 0;
this.ws = null;
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
console.log('Connected');
this.reconnectAttempts = 0;
this.onOpen?.();
};
this.ws.onmessage = (event) => this.onMessage?.(event);
this.ws.onclose = (event) => {
console.log('Connection closed');
this.onClose?.(event);
this.reconnect();
};
this.ws.onerror = (error) => this.onError?.(error);
}
reconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.log('Max reconnect attempts reached');
return;
}
this.reconnectAttempts++;
console.log(`Reconnecting... Attempt ${this.reconnectAttempts}`);
setTimeout(() => {
this.connect();
}, this.reconnectInterval);
}
send(data) {
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(typeof data === 'string' ? data : JSON.stringify(data));
}
}
close() {
this.ws?.close();
}
}
// 使用
const client = new WebSocketClient('ws://localhost:8080', {
reconnectInterval: 3000,
maxReconnectAttempts: 5,
});
client.onOpen = () => console.log('Connected!');
client.onMessage = (e) => console.log('Received:', e.data);
client.onClose = (e) => console.log('Closed:', e.code, e.reason);
client.connect();协议升级
HTTP → WebSocket
GET /ws HTTP/1.1
Host: localhost:8080
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYG3hQZBg==数据格式
JSON 消息协议
// 消息类型定义
const MessageType = {
CHAT: 'chat',
JOIN: 'join',
LEAVE: 'leave',
TYPING: 'typing',
PONG: 'pong',
};
// 发送
function send(type, payload) {
ws.send(JSON.stringify({ type, payload, timestamp: Date.now() }));
}
// 接收
ws.onmessage = (event) => {
const { type, payload, timestamp } = JSON.parse(event.data);
switch (type) {
case MessageType.CHAT:
handleChat(payload);
break;
case MessageType.JOIN:
handleJoin(payload);
break;
// ...
}
};二进制数据
// 发送 ArrayBuffer
const buffer = new ArrayBuffer(4);
const view = new DataView(buffer);
view.setUint8(0, 1);
ws.send(buffer);
// 接收
ws.binaryType = 'arraybuffer';
ws.onmessage = (event) => {
if (event.data instanceof ArrayBuffer) {
const view = new DataView(event.data);
console.log(view.getUint8(0));
}
};
// 发送 Blob
const blob = new Blob(['Hello'], { type: 'text/plain' });
ws.send(blob);安全
验证
// 通过 URL 参数或 WebSocket 握手头验证
const ws = new WebSocket(`ws://localhost:8080?token=${token}`);
// 服务端验证
wss.on('connection', (ws, req) => {
const url = new URL(req.url, 'http://localhost');
const token = url.searchParams.get('token');
if (!validateToken(token)) {
ws.close(4001, 'Unauthorized');
return;
}
// 继续处理
});WSS (WebSocket Secure)
// 生产环境必须使用 WSS
const ws = new WebSocket('wss://secure.example.com/ws');
// Node.js 服务端需要配置 TLS
const https = require('https');
const fs = require('fs');
const { WebSocketServer } = require('ws');
const server = https.createServer({
cert: fs.readFileSync('/path/to/cert.pem'),
key: fs.readFileSync('/path/to/key.pem'),
});
const wss = new WebSocketServer({ server });Socket.IO
Socket.IO 是一个封装了 WebSocket 的库,提供了更好的兼容性和更多功能。
客户端
import { io } from 'socket.io-client';
const socket = io('http://localhost:3000', {
transports: ['websocket'],
auth: { token: 'xxx' },
});
socket.on('connect', () => {
console.log('Connected');
});
socket.on('chat:message', (data) => {
console.log('New message:', data);
});
socket.emit('chat:send', { content: 'Hello!' });
socket.disconnect();服务端
const { Server } = require('socket.io');
const io = new Server(3000, {
cors: { origin: '*' },
});
io.on('connection', (socket) => {
console.log('User connected:', socket.id);
// 加入房间
socket.on('join:room', (roomId) => {
socket.join(roomId);
socket.to(roomId).emit('user:joined', socket.id);
});
// 发送消息到房间
socket.on('chat:message', ({ roomId, content }) => {
io.to(roomId).emit('chat:message', {
id: socket.id,
content,
timestamp: Date.now(),
});
});
socket.on('disconnect', () => {
console.log('User disconnected');
});
});应用场景
| 场景 | 说明 |
|---|---|
| 聊天应用 | 实时消息推送 |
| 游戏 | 实时状态同步 |
| 协作编辑 | 多用户实时协作 |
| 实时监控 | 实时数据展示 |
| 通知系统 | 即时通知推送 |
| 金融交易 | 实时行情数据 |
