WebSocket 原理与机制

该文章已生成可运行项目,
1. 核心原理

WebSocket 是 HTML5 定义的全双工通信协议,旨在解决 HTTP 协议的局限性(单向请求 - 响应、短连接),实现客户端与服务器的持久化双向通信。其核心特点包括:

  • 持久连接:通过一次 HTTP 握手建立连接后,保持 TCP 连接不关闭,避免频繁建立连接的开销。
  • 全双工通信:连接建立后,客户端和服务器可随时向对方发送数据,无需等待对方响应。
  • 轻量协议:数据帧格式简洁,比 HTTP 头部开销更小,适合实时场景(如聊天、实时数据监控、游戏等)。
2. 通信机制
(1)握手过程(基于 HTTP/HTTPS)

WebSocket 连接通过 HTTP 协议发起,本质是「协议升级」过程:

  1. 客户端发送 HTTP GET 请求,携带特殊头部表示要升级到 WebSocket 协议
  2. GET /ws HTTP/1.1
    Host: example.com
    Upgrade: websocket  # 声明要升级的协议
    Connection: Upgrade  # 声明连接要升级
    Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==  # 随机字符串,用于验证
    Sec-WebSocket-Version: 13  # 协议版本
  3. 服务器验证请求后,返回 101 Switching Protocols 响应,确认协议升级:
  4. 握手成功后,通信切换到 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>

运行说明

  1. 启动服务器

    • 安装依赖:npm install ws
    • 运行:node server.js(控制台显示「WebSocket 服务器启动,监听端口 8080」)
  2. 启动客户端

    • 用浏览器打开 client.html
    • 客户端会自动连接服务器,状态显示「已连接」
    • 可在输入框输入消息,点击「发送」或按回车,消息会广播给所有连接的客户端

核心功能说明

  1. 消息收发:客户端发送文本消息,服务器广播给所有在线客户端。
  2. 心跳检测
    • 客户端每 30 秒发送一次心跳消息(type: 'heartbeat')。
    • 服务器收到后立即回复心跳响应。
    • 若客户端 10 秒内未收到响应,判定连接失效并主动断开。
  3. 自动重连
    • 连接断开后(非主动关闭),客户端自动重试连接。
    • 重连间隔从 1 秒开始,每次失败后加倍(1s→2s→4s...),最大 10 秒。
    • 重连成功后重置间隔为 1 秒。

通过以上实现,可确保 WebSocket 连接的稳定性和实时性,适合构建各类实时交互应用。

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

codingMan_09

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值