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();
相关推荐
iuuia4 小时前
05--JavaScript基础语法(1)
开发语言·javascript·ecmascript
念你那丝微笑4 小时前
vue实现批量导出二维码到PDF(支持分页生成 PDF)
前端·vue.js·pdf
Renounce4 小时前
《Android Handler:线程间通信的核心实现》
前端
CAD老兵4 小时前
打造高性能二维图纸渲染引擎系列(一):Batched Geometry 助你轻松渲染百万实体
前端·webgl·three.js
前端老宋Running4 小时前
微信小程序的操作日志收集模块
前端
呼叫69454 小时前
为什么`for...of` 循环无法输出对象的自定义属性?
javascript
CAD老兵4 小时前
打造高性能二维图纸渲染引擎系列(三):高性能 CAD 文本渲染背后的隐藏工程
前端·webgl·three.js
CAD老兵4 小时前
打造高性能二维图纸渲染引擎系列(二):创建结构化和可扩展的渲染场景
前端·webgl·three.js
王木风4 小时前
1分钟理解什么是MySQL的Buffer Pool和LRU 算法?
前端·mysql