我将介绍WebSocket的服务端和客户端实现:
- 服务端实现 (Node.js + WS)
// server.js
const WebSocket = require('ws');
const http = require('http');
// 创建HTTP服务器
const server = http.createServer();
// 创建WebSocket服务器
const wss = new WebSocket.Server({ server });
// 存储所有连接的客户端
const clients = new Map();
// 处理连接
wss.on('connection', (ws, req) => {
const clientId = Date.now();
const clientInfo = {
id: clientId,
ws: ws,
lastHeartbeat: Date.now()
};
// 存储客户端连接
clients.set(clientId, clientInfo);
console.log(`Client ${clientId} connected`);
// 发送欢迎消息
ws.send(JSON.stringify({
type: 'welcome',
message: `Welcome client ${clientId}`,
clientId: clientId
}));
// 处理消息
ws.on('message', (message) => {
try {
const data = JSON.parse(message);
// 处理心跳
if (data.type === 'heartbeat') {
clientInfo.lastHeartbeat = Date.now();
ws.send(JSON.stringify({ type: 'heartbeat', message: 'pong' }));
return;
}
// 处理其他消息类型
switch (data.type) {
case 'broadcast':
broadcast(data.message, clientId);
break;
case 'private':
sendPrivateMessage(data.targetId, data.message, clientId);
break;
default:
console.log(`Received message from client ${clientId}:`, data);
}
} catch (error) {
console.error('Error processing message:', error);
}
});
// 处理关闭连接
ws.on('close', () => {
console.log(`Client ${clientId} disconnected`);
clients.delete(clientId);
broadcast({
type: 'system',
message: `Client ${clientId} left`
});
});
// 处理错误
ws.on('error', (error) => {
console.error(`Client ${clientId} error:`, error);
clients.delete(clientId);
});
});
// 广播消息给所有客户端
function broadcast(message, senderId) {
clients.forEach((client, id) => {
if (id !== senderId && client.ws.readyState === WebSocket.OPEN) {
client.ws.send(JSON.stringify({
type: 'broadcast',
senderId: senderId,
message: message
}));
}
});
}
// 发送私聊消息
function sendPrivateMessage(targetId, message, senderId) {
const targetClient = clients.get(parseInt(targetId));
if (targetClient && targetClient.ws.readyState === WebSocket.OPEN) {
targetClient.ws.send(JSON.stringify({
type: 'private',
senderId: senderId,
message: message
}));
}
}
// 清理断开的连接
setInterval(() => {
const now = Date.now();
clients.forEach((client, id) => {
if (now - client.lastHeartbeat > 30000) { // 30秒超时
console.log(`Client ${id} timed out`);
client.ws.terminate();
clients.delete(id);
}
});
}, 10000);
// 启动服务器
const PORT = process.env.PORT || 8080;
server.listen(PORT, () => {
console.log(`WebSocket server is running on port ${PORT}`);
});
- 前端实现
// websocket-client.js
class WebSocketClient {
constructor(url) {
this.url = url;
this.ws = null;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.reconnectInterval = 3000;
this.heartbeatInterval = null;
this.listeners = new Map();
this.clientId = null;
}
// 连接
connect() {
try {
this.ws = new WebSocket(this.url);
this.attachEventListeners();
} catch (error) {
console.error('WebSocket connection error:', error);
this.handleReconnect();
}
}
// 添加事件监听器
attachEventListeners() {
this.ws.onopen = () => {
console.log('WebSocket connected');
this.reconnectAttempts = 0;
this.startHeartbeat();
this.emit('connect');
};
this.ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
// 处理欢迎消息
if (data.type === 'welcome') {
this.clientId = data.clientId;
}
// 触发消息事件
this.emit('message', data);
// 处理特定类型的消息
if (this.listeners.has(data.type)) {
this.listeners.get(data.type).forEach(callback => callback(data));
}
} catch (error) {
console.error('Error parsing message:', error);
}
};
this.ws.onclose = () => {
console.log('WebSocket disconnected');
this.stopHeartbeat();
this.emit('disconnect');
this.handleReconnect();
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
this.emit('error', error);
};
}
// 重连机制
handleReconnect() {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
console.log(`Reconnecting... Attempt ${this.reconnectAttempts}`);
setTimeout(() => this.connect(), this.reconnectInterval);
} else {
console.error('Max reconnection attempts reached');
this.emit('reconnectFailed');
}
}
// 心跳机制
startHeartbeat() {
this.heartbeatInterval = setInterval(() => {
if (this.ws.readyState === WebSocket.OPEN) {
this.send({ type: 'heartbeat', message: 'ping' });
}
}, 15000); // 每15秒发送一次心跳
}
stopHeartbeat() {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
this.heartbeatInterval = null;
}
}
// 发送消息
send(data) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(data));
} else {
console.error('WebSocket is not connected');
}
}
// 发送广播消息
broadcast(message) {
this.send({
type: 'broadcast',
message: message
});
}
// 发送私聊消息
sendPrivateMessage(targetId, message) {
this.send({
type: 'private',
targetId: targetId,
message: message
});
}
// 监听特定类型的消息
on(type, callback) {
if (!this.listeners.has(type)) {
this.listeners.set(type, new Set());
}
this.listeners.get(type).add(callback);
}
// 移除监听器
off(type, callback) {
if (this.listeners.has(type)) {
this.listeners.get(type).delete(callback);
}
}
// 触发事件
emit(type, data) {
if (this.listeners.has(type)) {
this.listeners.get(type).forEach(callback => callback(data));
}
}
// 关闭连接
close() {
if (this.ws) {
this.ws.close();
}
this.stopHeartbeat();
}
// 获取连接状态
getState() {
return this.ws ? this.ws.readyState : WebSocket.CLOSED;
}
// 获取客户端ID
getClientId() {
return this.clientId;
}
}
// 使用示例
const ws = new WebSocketClient('ws://localhost:8080');
// 连接成功监听
ws.on('connect', () => {
console.log('Connected to server');
});
// 接收消息监听
ws.on('message', (data) => {
console.log('Received message:', data);
});
// 监听广播消息
ws.on('broadcast', (data) => {
console.log('Broadcast message:', data);
});
// 监听私聊消息
ws.on('private', (data) => {
console.log('Private message:', data);
});
// 连接断开监听
ws.on('disconnect', () => {
console.log('Disconnected from server');
});
// 错误监听
ws.on('error', (error) => {
console.error('WebSocket error:', error);
});
// 建立连接
ws.connect();
// 发送消息示例
ws.broadcast('Hello everyone!');
ws.sendPrivateMessage(123, 'Hello user 123!');
// 关闭连接
// ws.close();
- 聊天室示例
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Chat</title>
<style>
.chat-container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.messages {
height: 400px;
overflow-y: auto;
border: 1px solid #ccc;
padding: 10px;
margin-bottom: 10px;
}
.message {
margin-bottom: 10px;
padding: 5px;
}
.message.system { color: #666; }
.message.broadcast { color: #333; }
.message.private { color: #0066cc; }
.input-container {
display: flex;
gap: 10px;
}
input[type="text"] {
flex: 1;
padding: 5px;
}
button {
padding: 5px 10px;
}
</style>
</head>
<body>
<div class="chat-container">
<div class="messages" id="messages"></div>
<div class="input-container">
<input type="text" id="messageInput" placeholder="Type your message...">
<input type="text" id="targetInput" placeholder="Target ID (for private message)">
<button onclick="sendMessage()">Send</button>
<button onclick="sendPrivate()">Send Private</button>
</div>
</div>
<script>
const ws = new WebSocketClient('ws://localhost:8080');
const messagesDiv = document.getElementById('messages');
const messageInput = document.getElementById('messageInput');
const targetInput = document.getElementById('targetInput');
function addMessage(message, type = 'broadcast') {
const messageElement = document.createElement('div');
messageElement.classList.add('message', type);
messageElement.textContent = message;
messagesDiv.appendChild(messageElement);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
ws.on('connect', () => {
addMessage('Connected to chat server', 'system');
});
ws.on('message', (data) => {
if (data.type === 'welcome') {
addMessage(`Your client ID is: ${data.clientId}`, 'system');
}
});
ws.on('broadcast', (data) => {
addMessage(`${data.senderId}: ${data.message}`, 'broadcast');
});
ws.on('private', (data) => {
addMessage(`Private from ${data.senderId}: ${data.message}`, 'private');
});
ws.on('disconnect', () => {
addMessage('Disconnected from server', 'system');
});
function sendMessage() {
const message = messageInput.value.trim();
if (message) {
ws.broadcast(message);
addMessage(`You: ${message}`, 'broadcast');
messageInput.value = '';
}
}
function sendPrivate() {
const message = messageInput.value.trim();
const targetId = targetInput.value.trim();
if (message && targetId) {
ws.sendPrivateMessage(targetId, message);
addMessage(`You to ${targetId}: ${message}`, 'private');
messageInput.value = '';
}
}
messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
sendMessage();
}
});
ws.connect();
</script>
</body>
</html>
使用WebSocket时需要注意以下几点:
- 心跳机制
- 保持连接活跃
- 检测连接状态
- 及时发现断开连接
- 重连机制
- 自动重连
- 重连次数限制
- 重连间隔
- 消息格式
- 统一消息格式
- 消息类型区分
- 错误处理
- 安全性
- 验证机制
- 消息加密
- 访问控制
- 性能优化
- 消息压缩
- 连接池管理
- 负载均衡
- 降级方案
- 长轮询备选
- AJAX降级
- 错误处理
评论区