1. 核心原理
WebSocket 是 HTML5 定义的全双工通信协议,旨在解决 HTTP 协议的局限性(单向请求 - 响应、短连接),实现客户端与服务器的持久化双向通信。其核心特点包括:
- 持久连接:通过一次 HTTP 握手建立连接后,保持 TCP 连接不关闭,避免频繁建立连接的开销。
- 全双工通信:连接建立后,客户端和服务器可随时向对方发送数据,无需等待对方响应。
- 轻量协议:数据帧格式简洁,比 HTTP 头部开销更小,适合实时场景(如聊天、实时数据监控、游戏等)。
2. 通信机制
(1)握手过程(基于 HTTP/HTTPS)
WebSocket 连接通过 HTTP 协议发起,本质是「协议升级」过程:
- 客户端发送 HTTP GET 请求,携带特殊头部表示要升级到 WebSocket 协议
-
GET /ws HTTP/1.1 Host: example.com Upgrade: websocket # 声明要升级的协议 Connection: Upgrade # 声明连接要升级 Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== # 随机字符串,用于验证 Sec-WebSocket-Version: 13 # 协议版本 - 服务器验证请求后,返回
101 Switching Protocols响应,确认协议升级: - 握手成功后,通信切换到 WebSocket 协议,使用 TCP 连接直接传输数据。
(2)数据帧格式
WebSocket 数据通过「帧」传输,帧结构包含:
FIN:1 位,标识是否为消息的最后一帧。Opcode:4 位,标识帧类型(如文本帧0x1、二进制帧0x2、ping 帧0x9、pong 帧0xA、关闭帧0x8)。Mask:1 位,客户端发送的帧必须设置为 1(带掩码),服务器发送的帧为 0。Payload Data:实际传输的数据(文本或二进制)。
(3)心跳与重连机制
- 心跳(Heartbeat):用于检测连接是否存活。客户端定期发送「ping」帧(或自定义心跳消息),服务器收到后返回「pong」帧。若超时未收到 pong,判定连接断开。
- 重连(Reconnection):连接断开后,客户端自动尝试重新连接,通常采用「指数退避」策略(重试间隔逐渐增加,如 1s→2s→4s...),避免频繁重试消耗资源。
完整应用样例(含心跳与重连)
以下实现一个基于 Node.js(服务器)和浏览器(客户端)的 WebSocket 应用,包含消息收发、心跳检测和自动重连功能。
1. 服务器端(Node.js + ws 库)
使用 ws 库(WebSocket 服务器实现),处理连接、消息、心跳和断开事件。
// 安装依赖:npm install ws
const WebSocket = require('ws');
// 创建WebSocket服务器,监听8080端口
const wss = new WebSocket.Server({ port: 8080 });
console.log('WebSocket服务器启动,监听端口8080');
// 监听客户端连接
wss.on('connection', (ws) => {
console.log('新客户端连接');
// 处理客户端发送的消息
ws.on('message', (data) => {
const message = JSON.parse(data.toString());
console.log('收到消息:', message);
// 若为心跳消息,回复确认
if (message.type === 'heartbeat') {
ws.send(JSON.stringify({
type: 'heartbeat',
timestamp: Date.now(),
message: 'pong'
}));
} else {
// 普通消息,广播给所有客户端
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({
type: 'message',
content: message.content,
time: new Date().toLocaleTimeString()
}));
}
});
}
});
// 处理连接关闭
ws.on('close', () => {
console.log('客户端断开连接');
});
// 处理错误
ws.on('error', (err) => {
console.error('连接错误:', err);
});
});
2. 客户端(HTML + JavaScript)
实现连接管理、消息收发、心跳检测(定时发送心跳)和自动重连(指数退避策略)。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>WebSocket 客户端</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<style>
.message-list {
height: 400px;
overflow-y: auto;
}
</style>
</head>
<body class="bg-gray-100 p-4">
<div class="max-w-2xl mx-auto bg-white rounded-lg shadow-md p-6">
<h1 class="text-2xl font-bold mb-4 flex items-center">
<i class="fa fa-comments text-blue-500 mr-2"></i>WebSocket 实时通信
</h1>
<!-- 连接状态 -->
<div id="status" class="mb-4 p-2 bg-yellow-100 text-yellow-800 rounded">
<i class="fa fa-circle-o-notch fa-spin mr-2"></i>正在连接...
</div>
<!-- 消息列表 -->
<div class="message-list border border-gray-200 rounded-lg p-3 mb-4 bg-gray-50">
<div id="messages" class="space-y-2"></div>
</div>
<!-- 发送消息区域 -->
<div class="flex gap-2">
<input
type="text"
id="messageInput"
placeholder="输入消息..."
class="flex-1 px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<button
id="sendBtn"
class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 disabled:bg-gray-400"
disabled
>
<i class="fa fa-paper-plane mr-1"></i>发送
</button>
</div>
</div>
<script>
// 客户端配置
const WS_URL = 'ws://localhost:8080';
const HEARTBEAT_INTERVAL = 30000; // 心跳间隔(30秒)
const RECONNECT_DELAY_BASE = 1000; // 初始重连延迟(1秒)
const MAX_RECONNECT_DELAY = 10000; // 最大重连延迟(10秒)
// 状态变量
let ws; // WebSocket实例
let heartbeatTimer; // 心跳定时器
let lastHeartbeatTime = 0; // 最后一次收到心跳响应的时间
let reconnectDelay = RECONNECT_DELAY_BASE; // 当前重连延迟
let isConnecting = false; // 是否正在连接中
// DOM元素
const statusEl = document.getElementById('status');
const messagesEl = document.getElementById('messages');
const messageInput = document.getElementById('messageInput');
const sendBtn = document.getElementById('sendBtn');
// 初始化连接
connect();
// 连接WebSocket服务器
function connect() {
if (isConnecting) return;
isConnecting = true;
updateStatus('正在连接...', 'yellow');
// 创建WebSocket实例
ws = new WebSocket(WS_URL);
// 连接成功
ws.onopen = () => {
console.log('连接成功');
isConnecting = false;
reconnectDelay = RECONNECT_DELAY_BASE; // 重置重连延迟
updateStatus('已连接', 'green');
sendBtn.disabled = false;
// 启动心跳检测
startHeartbeat();
};
// 收到消息
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
handleMessage(message);
};
// 连接关闭
ws.onclose = (event) => {
console.log('连接关闭,代码:', event.code, '原因:', event.reason);
isConnecting = false;
sendBtn.disabled = true;
stopHeartbeat();
// 异常关闭才重连(正常关闭代码1000)
if (event.code !== 1000) {
updateStatus(`连接断开,${reconnectDelay/1000}秒后重试...`, 'red');
setTimeout(connect, reconnectDelay);
// 重连延迟指数增加(但不超过最大值)
reconnectDelay = Math.min(reconnectDelay * 2, MAX_RECONNECT_DELAY);
} else {
updateStatus('连接已关闭', 'gray');
}
};
// 连接错误
ws.onerror = (error) => {
console.error('连接错误:', error);
updateStatus('连接错误', 'red');
};
}
// 处理收到的消息
function handleMessage(message) {
if (message.type === 'heartbeat') {
// 收到心跳响应,更新时间
lastHeartbeatTime = Date.now();
console.log('收到心跳响应:', message);
} else if (message.type === 'message') {
// 显示普通消息
const messageEl = document.createElement('div');
messageEl.className = 'p-2 bg-blue-50 rounded border-l-4 border-blue-500';
messageEl.innerHTML = `
<div class="text-sm text-gray-500">${message.time}</div>
<div class="mt-1">${message.content}</div>
`;
messagesEl.appendChild(messageEl);
// 滚动到底部
messagesEl.scrollTop = messagesEl.scrollHeight;
}
}
// 发送消息
function sendMessage() {
const content = messageInput.value.trim();
if (!content || !ws || ws.readyState !== WebSocket.OPEN) return;
ws.send(JSON.stringify({
type: 'message',
content: content
}));
messageInput.value = '';
}
// 启动心跳检测
function startHeartbeat() {
// 定时发送心跳
heartbeatTimer = setInterval(() => {
if (ws && ws.readyState === WebSocket.OPEN) {
// 发送心跳消息
ws.send(JSON.stringify({
type: 'heartbeat',
timestamp: Date.now()
}));
console.log('发送心跳');
// 检查是否超时(10秒内未收到响应)
const now = Date.now();
if (lastHeartbeatTime > 0 && now - lastHeartbeatTime > 10000) {
console.error('心跳超时,断开连接');
ws.close(1006, '心跳超时'); // 1006表示异常关闭
}
}
}, HEARTBEAT_INTERVAL);
}
// 停止心跳检测
function stopHeartbeat() {
if (heartbeatTimer) {
clearInterval(heartbeatTimer);
heartbeatTimer = null;
}
}
// 更新连接状态显示
function updateStatus(text, color) {
const colorClass = {
green: 'bg-green-100 text-green-800',
yellow: 'bg-yellow-100 text-yellow-800',
red: 'bg-red-100 text-red-800',
gray: 'bg-gray-100 text-gray-800'
}[color] || 'bg-gray-100 text-gray-800';
statusEl.className = `mb-4 p-2 rounded ${colorClass}`;
statusEl.innerHTML = text;
}
// 绑定发送按钮事件
sendBtn.addEventListener('click', sendMessage);
// 回车发送消息
messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') sendMessage();
});
</script>
</body>
</html>
运行说明
-
启动服务器:
- 安装依赖:
npm install ws - 运行:
node server.js(控制台显示「WebSocket 服务器启动,监听端口 8080」)
- 安装依赖:
-
启动客户端:
- 用浏览器打开
client.html - 客户端会自动连接服务器,状态显示「已连接」
- 可在输入框输入消息,点击「发送」或按回车,消息会广播给所有连接的客户端
- 用浏览器打开
核心功能说明
- 消息收发:客户端发送文本消息,服务器广播给所有在线客户端。
- 心跳检测:
- 客户端每 30 秒发送一次心跳消息(
type: 'heartbeat')。 - 服务器收到后立即回复心跳响应。
- 若客户端 10 秒内未收到响应,判定连接失效并主动断开。
- 客户端每 30 秒发送一次心跳消息(
- 自动重连:
- 连接断开后(非主动关闭),客户端自动重试连接。
- 重连间隔从 1 秒开始,每次失败后加倍(1s→2s→4s...),最大 10 秒。
- 重连成功后重置间隔为 1 秒。
通过以上实现,可确保 WebSocket 连接的稳定性和实时性,适合构建各类实时交互应用。
13万+

被折叠的 条评论
为什么被折叠?



