websocket的连接原理

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)主动关闭连接,流程:

  1. 发送方发送关闭帧(包含状态码和原因)。
  2. 接收方收到后返回确认关闭帧。
  3. 双方关闭 TCP 连接。

总结

WebSocket 连接的核心是 "HTTP 握手升级 + 持久 TCP 连接 + 帧格式传输":

  1. 通过 HTTP 协议完成握手,验证并升级为 WebSocket 协议。
  2. 基于 TCP 实现全双工通信,双方可随时发送数据。
  3. 采用帧格式传输数据,更高效、紧凑,适合实时场景。

这种机制让 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();
相关推荐
辻戋2 小时前
从零实现React Scheduler调度器
前端·react.js·前端框架
徐同保2 小时前
使用yarn@4.6.0装包,项目是react+vite搭建的,项目无法启动,报错:
前端·react.js·前端框架
Qrun3 小时前
Windows11安装nvm管理node多版本
前端·vscode·react.js·ajax·npm·html5
中国lanwp3 小时前
全局 npm config 与多环境配置
前端·npm·node.js
JELEE.4 小时前
Django登录注册完整代码(图片、邮箱验证、加密)
前端·javascript·后端·python·django·bootstrap·jquery
TeleostNaCl6 小时前
解决 Chrome 无法访问网页但无痕模式下可以访问该网页 的问题
前端·网络·chrome·windows·经验分享
前端大卫7 小时前
为什么 React 中的 key 不能用索引?
前端
你的人类朋友7 小时前
【Node】手动归还主线程控制权:解决 Node.js 阻塞的一个思路
前端·后端·node.js
小李小李不讲道理9 小时前
「Ant Design 组件库探索」五:Tabs组件
前端·react.js·ant design
毕设十刻9 小时前
基于Vue的学分预警系统98k51(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
前端·数据库·vue.js