WebSocket 是一种全双工通信协议,基于 TCP 连接实现,允许客户端和服务器之间建立持久连接,实现双向实时数据传输。与 HTTP 的 "请求 - 响应" 模式不同,WebSocket 一旦连接建立,双方可随时主动发送数据,无需重复建立连接。
一、WebSocket 连接建立原理(握手过程)
WebSocket 连接的建立需要经过HTTP 握手 和协议升级两个阶段,具体流程如下:
1. 客户端发起握手请求(HTTP 协议)
客户端通过 HTTP 请求告知服务器 "希望升级到 WebSocket 协议",请求头包含特殊字段:
js
GET /ws-endpoint HTTP/1.1
Host: example.com
Upgrade: websocket # 核心:请求升级到 WebSocket 协议
Connection: Upgrade # 配合 Upgrade,标识这是升级请求
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== # 随机字符串,用于验证服务器
Sec-WebSocket-Version: 13 # WebSocket 协议版本
Sec-WebSocket-Key
:客户端生成的随机 Base64 字符串,用于后续服务器验证。
2. 服务器响应协议升级(HTTP 101 状态码)
服务器收到请求后,若同意升级,会返回 101 Switching Protocols
响应,并完成验证:
js
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= # 验证后的字符串
- 验证逻辑 :服务器将客户端的
Sec-WebSocket-Key
与固定字符串258EAFA5-E914-47DA-95CA-C5AB0DC85B11
拼接,计算 SHA-1 哈希后再进行 Base64 编码,得到Sec-WebSocket-Accept
,客户端会验证该值是否正确,确保连接安全。
3. 协议切换,建立持久连接
握手成功后,HTTP 连接正式升级为 WebSocket 连接,后续数据传输不再使用 HTTP 协议,而是采用WebSocket 帧(Frame) 格式,实现双向实时通信。
二、WebSocket 数据传输原理
1. 帧格式(Frame Format)
WebSocket 数据以 "帧" 为单位传输,每个帧包含以下关键部分(简化):
- Opcode (操作码):标识帧类型(如
0x1
表示文本帧,0x2
表示二进制帧)。 - Payload Length:数据长度(0-125 字节直接表示,更长需扩展)。
- Mask(掩码):客户端发送的数据必须用 32 位掩码加密(防止代理缓存污染),服务器发送的数据无需掩码。
- Payload Data:实际传输的数据(文本或二进制)。
2. 全双工通信
连接建立后,客户端和服务器可随时向对方发送帧:
- 客户端 → 服务器:数据帧需带掩码。
- 服务器 → 客户端:数据帧不带掩码。
- 双方收到帧后,根据 opcode 解析数据(文本帧需转 UTF-8,二进制帧直接使用)。
三、与 HTTP 的核心区别
特性 | HTTP | WebSocket |
---|---|---|
连接方式 | 短连接(请求 - 响应后关闭) | 长连接(一次建立,持久有效) |
通信方向 | 单向(客户端请求 → 服务器响应) | 双向(双方可主动发送数据) |
数据格式 | 文本(HTTP 头 + 体) | 帧格式(二进制 / 文本,更紧凑) |
适用场景 | 页面加载、API 调用 | 实时聊天、实时数据更新(如股票、游戏) |
四、WebSocket 连接关闭
任何一方可发送关闭帧 (opcode 0x8
)主动关闭连接,流程:
- 发送方发送关闭帧(包含状态码和原因)。
- 接收方收到后返回确认关闭帧。
- 双方关闭 TCP 连接。
总结
WebSocket 连接的核心是 "HTTP 握手升级 + 持久 TCP 连接 + 帧格式传输":
- 通过 HTTP 协议完成握手,验证并升级为 WebSocket 协议。
- 基于 TCP 实现全双工通信,双方可随时发送数据。
- 采用帧格式传输数据,更高效、紧凑,适合实时场景。
这种机制让 WebSocket 成为实时通信(如聊天、直播、协作工具)的首选方案。
ws连接脚本
js
class AdvancedWebSocketClient {
/**
* 初始化WebSocket客户端
* @param {string} url - WebSocket服务端地址
* @param {Object} options - 配置选项
* @param {number} options.reconnectInterval - 重连间隔时间(ms),默认3000
* @param {number} options.heartbeatInterval - 心跳间隔时间(ms),默认5000
* @param {number} options.maxReconnectAttempts - 最大重连次数,默认无限次
* @param {number} options.maxHeartbeatFails - 最大心跳失败次数,默认5
*/
constructor(url, options = {}) {
this.url = url;
this.ws = null;
this.isConnected = false;
// 定时器管理
this.reconnectTimer = null;
this.heartbeatTimer = null;
this.heartbeatTimeoutTimer = null;
// 状态跟踪
this.reconnectAttempts = 0;
this.heartbeatFailCount = 0; // 心跳失败计数器
// 配置参数
this.reconnectInterval = options.reconnectInterval || 3000;
this.heartbeatInterval = options.heartbeatInterval || 5000;
this.maxReconnectAttempts = options.maxReconnectAttempts;
this.maxHeartbeatFails = options.maxHeartbeatFails || 5; // 心跳失败阈值
// 事件回调存储
this.eventCallbacks = {
open: [],
message: [],
error: [],
close: [],
heartbeatFail: [] // 新增心跳失败事件
};
}
/**
* 连接WebSocket服务器
*/
connect() {
if (this.isConnected) {
console.log('已经处于连接状态');
return;
}
// 清理旧连接
if (this.ws) {
this.ws.close(1000, '重新连接');
}
try {
this.ws = new WebSocket(this.url);
this.ws.onopen = (event) => {
console.log('WebSocket连接成功');
this.isConnected = true;
this.reconnectAttempts = 0;
this.heartbeatFailCount = 0; // 重置心跳失败计数
this.startHeartbeat();
this.eventCallbacks.open.forEach(callback => callback(event));
};
this.ws.onmessage = (event) => {
// 检测是否是心跳响应
try {
const data = JSON.parse(event.data);
if (data.type === 'heartbeat_ack') {
console.log('收到心跳响应');
this.heartbeatFailCount = 0; // 重置失败计数
this.clearHeartbeatTimeout();
return; // 不触发普通消息回调
}
} catch (e) {
// 非JSON格式消息,不作为心跳响应处理
}
// 普通消息处理
console.log('收到消息:', event.data);
this.eventCallbacks.message.forEach(callback => callback(event));
};
this.ws.onerror = (error) => {
console.error('WebSocket错误:', error);
this.eventCallbacks.error.forEach(callback => callback(error));
};
this.ws.onclose = (event) => {
console.log(`WebSocket关闭,代码: ${event.code}, 原因: ${event.reason}`);
this.isConnected = false;
this.stopHeartbeat();
this.eventCallbacks.close.forEach(callback => callback(event));
// 正常关闭(1000)不重连
if (event.code !== 1000) {
this.attemptReconnect();
}
};
} catch (error) {
console.error('WebSocket连接失败:', error);
this.eventCallbacks.error.forEach(callback => callback(error));
this.attemptReconnect();
}
}
/**
* 尝试重连
*/
attemptReconnect() {
if (this.maxReconnectAttempts && this.reconnectAttempts >= this.maxReconnectAttempts) {
console.log(`已达到最大重连次数(${this.maxReconnectAttempts}),停止重连`);
return;
}
this.reconnectAttempts++;
console.log(`正在进行第${this.reconnectAttempts}次重连...`);
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
}
this.reconnectTimer = setTimeout(() => {
this.connect();
}, this.reconnectInterval);
}
/**
* 启动心跳检测
*/
startHeartbeat() {
this.stopHeartbeat(); // 先清理旧定时器
this.heartbeatTimer = setInterval(() => {
if (this.isConnected && this.ws) {
try {
// 发送心跳包
this.ws.send(JSON.stringify({
type: 'heartbeat',
timestamp: Date.now()
}));
console.log('发送心跳包');
// 设置心跳超时检测
this.heartbeatTimeoutTimer = setTimeout(() => {
this.handleHeartbeatFail();
}, this.heartbeatInterval * 0.8); // 超时时间设为心跳间隔的80%
} catch (error) {
console.error('发送心跳包失败:', error);
this.handleHeartbeatFail();
}
}
}, this.heartbeatInterval);
}
/**
* 处理心跳失败
*/
handleHeartbeatFail() {
this.heartbeatFailCount++;
console.warn(`心跳检测失败,累计次数: ${this.heartbeatFailCount}`);
// 触发心跳失败事件
this.eventCallbacks.heartbeatFail.forEach(callback =>
callback(this.heartbeatFailCount, this.maxHeartbeatFails)
);
// 达到最大失败次数,触发重连
if (this.heartbeatFailCount >= this.maxHeartbeatFails) {
console.error(`连续${this.maxHeartbeatFails}次心跳失败,尝试重连...`);
this.close(1011, '心跳检测失败'); // 主动关闭连接
this.attemptReconnect();
}
}
/**
* 清除心跳超时定时器
*/
clearHeartbeatTimeout() {
if (this.heartbeatTimeoutTimer) {
clearTimeout(this.heartbeatTimeoutTimer);
this.heartbeatTimeoutTimer = null;
}
}
/**
* 停止心跳检测
*/
stopHeartbeat() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
}
this.clearHeartbeatTimeout();
console.log('已停止心跳检测');
}
/**
* 发送消息
* @param {string|Object} data - 要发送的数据
*/
send(data) {
if (!this.isConnected || !this.ws) {
console.error('WebSocket未连接,无法发送消息');
return false;
}
try {
const sendData = typeof data === 'object' ? JSON.stringify(data) : data;
this.ws.send(sendData);
console.log('发送消息:', sendData);
return true;
} catch (error) {
console.error('发送消息失败:', error);
return false;
}
}
/**
* 关闭WebSocket连接
* @param {number} code - 关闭代码
* @param {string} reason - 关闭原因
*/
close(code = 1000, reason = '正常关闭') {
if (this.ws) {
this.ws.close(code, reason);
}
this.stopHeartbeat();
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
}
}
/**
* 注册事件回调
* @param {string} event - 事件名称
* @param {Function} callback - 回调函数
*/
on(event, callback) {
if (this.eventCallbacks[event]) {
this.eventCallbacks[event].push(callback);
} else {
console.warn(`不支持的事件类型: ${event}`);
}
}
/**
* 移除事件回调
* @param {string} event - 事件名称
* @param {Function} callback - 要移除的回调函数
*/
off(event, callback) {
if (this.eventCallbacks[event]) {
this.eventCallbacks[event] = this.eventCallbacks[event].filter(cb => cb !== callback);
}
}
}
使用
js
// 使用示例
const wsClient = new AdvancedWebSocketClient('wss://your-websocket-server.com', {
reconnectInterval: 3000, // 重连间隔3秒
heartbeatInterval: 5000, // 心跳间隔5秒
maxReconnectAttempts: 10, // 最大重连10次
maxHeartbeatFails: 5 // 心跳失败5次后重连
});
// 注册事件回调
wsClient.on('open', () => {
console.log('连接已建立,可以发送消息了');
wsClient.send({ type: 'init', data: 'client ready' });
});
wsClient.on('message', (event) => {
try {
const data = JSON.parse(event.data);
console.log('处理业务消息:', data);
// 处理业务逻辑...
} catch (error) {
console.error('解析消息失败:', error);
console.log('原始消息:', event.data);
}
});
wsClient.on('heartbeatFail', (currentCount, maxCount) => {
console.log(`心跳检测失败(${currentCount}/${maxCount})`);
// 可以在这里添加UI提示,如"连接不稳定..."
});
wsClient.on('error', (error) => {
console.error('发生错误:', error);
});
wsClient.on('close', (event) => {
console.log(`连接已关闭: ${event.reason}`);
});
// 开始连接
wsClient.connect();