轩辕李的博客 轩辕李的博客
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • JavaScript
  • TypeScript
  • Node.js
  • Vue.js
  • 前端工程化
  • 浏览器与Web API
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

轩辕李

勇猛精进,星辰大海
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • JavaScript
  • TypeScript
  • Node.js
  • Vue.js
  • 前端工程化
  • 浏览器与Web API
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • JavaScript

  • TypeScript

  • Node.js

  • Vue.js

  • 工程化

  • 浏览器与Web API

    • HTML基础-语义化标签与文档结构
    • HTML基础-文本与排版标签
    • HTML基础-列表与表格
    • HTML表单-Input类型详解
    • HTML表单-表单元素与验证
    • HTML交互-多媒体元素
    • HTML工程化-模板与组件化
    • HTML工程化-性能优化
    • CSS基础-选择器与优先级
    • CSS基础-盒模型与布局基础
    • CSS基础-单位与颜色系统
    • CSS基础-文本与字体
    • CSS基础-背景、列表与表格样式
    • CSS布局-Flexbox完全指南
    • CSS布局-Grid网格布局
    • CSS布局-响应式设计实践
    • CSS进阶-动画与过渡
    • CSS进阶-渐变与阴影效果
    • CSS进阶-Transform与3D变换
    • CSS进阶-滤镜与混合模式
    • 现代CSS-CSS预处理器对比
    • 现代CSS-CSS-in-JS方案
    • 现代CSS-原子化CSS与Tailwind
    • CSS工程化-架构与规范
    • CSS工程化-性能优化
    • CSS工程化-PostCSS实战指南
    • Web API基础-DOM操作完全指南
    • Web API基础-事件处理与委托
    • Web API基础-BOM与浏览器环境
    • Web API存储-客户端存储方案
    • Web API网络-HTTP请求详解
    • Web API网络-实时通信方案
      • 一、WebSocket基础
        • 1.1 WebSocket特点
        • 1.2 基本使用
        • 1.3 readyState状态
        • 1.4 发送不同类型数据
      • 二、WebSocket高级用法
        • 2.1 封装WebSocket类
        • 2.2 消息队列
        • 2.3 消息确认机制
      • 三、Server-Sent Events(SSE)
        • 3.1 SSE特点
        • 3.2 基本使用
        • 3.3 readyState状态
        • 3.4 服务端实现(Node.js示例)
        • 3.5 封装EventSource类
      • 四、WebSocket vs SSE对比
        • 4.1 对比表格
        • 4.2 选择建议
      • 五、综合示例
      • 六、最佳实践
        • 6.1 WebSocket最佳实践
        • 6.2 SSE最佳实践
        • 6.3 通用最佳实践
      • 七、总结
    • Web API交互-用户体验增强
    • HTML&CSS历代版本新特性
  • 前端
  • 浏览器与Web API
轩辕李
2019-10-24
目录

Web API网络-实时通信方案

# Web API网络-实时通信方案

实时通信是现代Web应用的重要特性。本文将详细介绍WebSocket和Server-Sent Events(SSE)两种主流的实时通信方案,包括连接建立、消息传递、错误处理、心跳机制等实用技巧。

# 一、WebSocket基础

WebSocket提供了全双工通信通道,允许服务器主动向客户端推送数据。

# 1.1 WebSocket特点

  • 全双工通信:客户端和服务器可以同时发送和接收消息
  • 持久连接:建立连接后保持打开状态
  • 低延迟:无需轮询,消息即时送达
  • 二进制支持:可传输文本和二进制数据
  • 协议升级:从HTTP升级到WebSocket协议

# 1.2 基本使用

// 创建WebSocket连接
const ws = new WebSocket('ws://localhost:8080');
// 或使用加密连接
// const ws = new WebSocket('wss://example.com');

// 连接打开
ws.addEventListener('open', function(event) {
  console.log('WebSocket连接已建立');
  
  // 发送消息
  ws.send('Hello Server!');
  ws.send(JSON.stringify({ type: 'greeting', message: 'Hello' }));
});

// 接收消息
ws.addEventListener('message', function(event) {
  console.log('收到消息:', event.data);
  
  // 如果是JSON数据
  try {
    const data = JSON.parse(event.data);
    console.log('解析后的数据:', data);
  } catch (e) {
    console.log('不是JSON格式');
  }
});

// 连接关闭
ws.addEventListener('close', function(event) {
  console.log('WebSocket连接已关闭');
  console.log('关闭代码:', event.code);
  console.log('关闭原因:', event.reason);
  console.log('是否正常关闭:', event.wasClean);
});

// 连接错误
ws.addEventListener('error', function(event) {
  console.error('WebSocket错误:', event);
});

// 主动关闭连接
// ws.close();
// ws.close(1000, '正常关闭');

# 1.3 readyState状态

console.log(ws.readyState);

// 0: CONNECTING - 正在连接
// 1: OPEN - 连接已建立,可以通信
// 2: CLOSING - 连接正在关闭
// 3: CLOSED - 连接已关闭或无法打开

// 检查连接状态
function checkConnectionState() {
  switch(ws.readyState) {
    case WebSocket.CONNECTING:
      console.log('正在连接...');
      break;
    case WebSocket.OPEN:
      console.log('连接已建立');
      break;
    case WebSocket.CLOSING:
      console.log('连接正在关闭...');
      break;
    case WebSocket.CLOSED:
      console.log('连接已关闭');
      break;
  }
}

# 1.4 发送不同类型数据

const ws = new WebSocket('ws://localhost:8080');

ws.addEventListener('open', function() {
  // 1. 发送文本
  ws.send('Hello');
  
  // 2. 发送JSON
  ws.send(JSON.stringify({
    type: 'message',
    content: 'Hello World'
  }));
  
  // 3. 发送Blob
  const blob = new Blob(['binary data'], { type: 'application/octet-stream' });
  ws.send(blob);
  
  // 4. 发送ArrayBuffer
  const buffer = new ArrayBuffer(8);
  const view = new Uint8Array(buffer);
  view[0] = 1;
  view[1] = 2;
  ws.send(buffer);
  
  // 5. 发送TypedArray
  const uint8 = new Uint8Array([1, 2, 3, 4]);
  ws.send(uint8);
});

// 接收不同类型数据
ws.addEventListener('message', function(event) {
  // 文本数据
  if (typeof event.data === 'string') {
    console.log('文本消息:', event.data);
  }
  
  // Blob数据
  if (event.data instanceof Blob) {
    console.log('Blob消息:', event.data);
    
    // 读取Blob
    const reader = new FileReader();
    reader.onload = function() {
      console.log('Blob内容:', reader.result);
    };
    reader.readAsText(event.data);
  }
  
  // ArrayBuffer数据
  if (event.data instanceof ArrayBuffer) {
    console.log('ArrayBuffer消息:', event.data);
    const view = new Uint8Array(event.data);
    console.log('数据:', view);
  }
});

// 设置接收数据格式
ws.binaryType = 'arraybuffer'; // 或 'blob'(默认)

# 二、WebSocket高级用法

# 2.1 封装WebSocket类

class WebSocketClient {
  constructor(url, options = {}) {
    this.url = url;
    this.options = {
      reconnect: true,           // 是否自动重连
      reconnectInterval: 3000,   // 重连间隔
      reconnectAttempts: 5,      // 最大重连次数
      heartbeatInterval: 30000,  // 心跳间隔
      heartbeatMessage: 'ping',  // 心跳消息
      ...options
    };
    
    this.ws = null;
    this.reconnectCount = 0;
    this.heartbeatTimer = null;
    this.isManualClose = false;
    
    this.messageHandlers = [];
    this.openHandlers = [];
    this.closeHandlers = [];
    this.errorHandlers = [];
  }
  
  // 连接
  connect() {
    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
      console.log('WebSocket已连接');
      return;
    }
    
    this.ws = new WebSocket(this.url);
    
    this.ws.addEventListener('open', (event) => {
      console.log('WebSocket连接成功');
      this.reconnectCount = 0;
      this.startHeartbeat();
      
      this.openHandlers.forEach(handler => handler(event));
    });
    
    this.ws.addEventListener('message', (event) => {
      // 处理心跳响应
      if (event.data === 'pong') {
        return;
      }
      
      this.messageHandlers.forEach(handler => handler(event));
    });
    
    this.ws.addEventListener('close', (event) => {
      console.log('WebSocket连接关闭');
      this.stopHeartbeat();
      
      this.closeHandlers.forEach(handler => handler(event));
      
      // 自动重连
      if (!this.isManualClose && this.options.reconnect) {
        this.reconnect();
      }
    });
    
    this.ws.addEventListener('error', (event) => {
      console.error('WebSocket错误');
      
      this.errorHandlers.forEach(handler => handler(event));
    });
  }
  
  // 重连
  reconnect() {
    if (this.reconnectCount >= this.options.reconnectAttempts) {
      console.log('达到最大重连次数');
      return;
    }
    
    this.reconnectCount++;
    console.log(`尝试重连 (${this.reconnectCount}/${this.options.reconnectAttempts})...`);
    
    setTimeout(() => {
      this.connect();
    }, this.options.reconnectInterval);
  }
  
  // 发送消息
  send(data) {
    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
      if (typeof data === 'object') {
        this.ws.send(JSON.stringify(data));
      } else {
        this.ws.send(data);
      }
    } else {
      console.error('WebSocket未连接');
    }
  }
  
  // 关闭连接
  close() {
    this.isManualClose = true;
    this.stopHeartbeat();
    
    if (this.ws) {
      this.ws.close();
    }
  }
  
  // 心跳检测
  startHeartbeat() {
    this.heartbeatTimer = setInterval(() => {
      if (this.ws && this.ws.readyState === WebSocket.OPEN) {
        this.ws.send(this.options.heartbeatMessage);
      }
    }, this.options.heartbeatInterval);
  }
  
  stopHeartbeat() {
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer);
      this.heartbeatTimer = null;
    }
  }
  
  // 事件监听
  onOpen(handler) {
    this.openHandlers.push(handler);
  }
  
  onMessage(handler) {
    this.messageHandlers.push(handler);
  }
  
  onClose(handler) {
    this.closeHandlers.push(handler);
  }
  
  onError(handler) {
    this.errorHandlers.push(handler);
  }
}

// 使用示例
const client = new WebSocketClient('ws://localhost:8080', {
  reconnect: true,
  reconnectInterval: 3000,
  reconnectAttempts: 5,
  heartbeatInterval: 30000
});

client.onOpen(() => {
  console.log('连接建立');
  client.send({ type: 'auth', token: 'abc123' });
});

client.onMessage((event) => {
  const data = JSON.parse(event.data);
  console.log('收到消息:', data);
});

client.onClose(() => {
  console.log('连接关闭');
});

client.onError((error) => {
  console.error('连接错误:', error);
});

client.connect();

# 2.2 消息队列

class WebSocketWithQueue extends WebSocketClient {
  constructor(url, options) {
    super(url, options);
    this.messageQueue = [];
  }
  
  connect() {
    super.connect();
    
    // 连接成功后发送队列中的消息
    this.onOpen(() => {
      this.flushQueue();
    });
  }
  
  send(data) {
    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
      super.send(data);
    } else {
      // 连接未建立,加入队列
      this.messageQueue.push(data);
    }
  }
  
  flushQueue() {
    while (this.messageQueue.length > 0) {
      const data = this.messageQueue.shift();
      super.send(data);
    }
  }
}

# 2.3 消息确认机制

class WebSocketWithAck extends WebSocketClient {
  constructor(url, options) {
    super(url, options);
    this.pendingMessages = new Map();
    this.messageId = 0;
  }
  
  sendWithAck(data, timeout = 5000) {
    return new Promise((resolve, reject) => {
      const id = ++this.messageId;
      
      const message = {
        id: id,
        data: data
      };
      
      this.send(message);
      
      const timer = setTimeout(() => {
        this.pendingMessages.delete(id);
        reject(new Error('消息发送超时'));
      }, timeout);
      
      this.pendingMessages.set(id, {
        resolve,
        reject,
        timer
      });
    });
  }
  
  handleAck(messageId, success, error) {
    const pending = this.pendingMessages.get(messageId);
    
    if (pending) {
      clearTimeout(pending.timer);
      this.pendingMessages.delete(messageId);
      
      if (success) {
        pending.resolve();
      } else {
        pending.reject(new Error(error || '消息处理失败'));
      }
    }
  }
}

// 使用示例
const wsWithAck = new WebSocketWithAck('ws://localhost:8080');

wsWithAck.onMessage((event) => {
  const data = JSON.parse(event.data);
  
  if (data.type === 'ack') {
    wsWithAck.handleAck(data.messageId, data.success, data.error);
  }
});

// 发送并等待确认
try {
  await wsWithAck.sendWithAck({ type: 'order', product: 'book' });
  console.log('消息发送成功');
} catch (error) {
  console.error('消息发送失败:', error.message);
}

# 三、Server-Sent Events(SSE)

SSE提供了服务器向客户端推送消息的单向通信机制。

# 3.1 SSE特点

  • 单向通信:只能服务器推送到客户端
  • 自动重连:连接断开后自动重连
  • 文本数据:只支持文本数据(通常是JSON)
  • 基于HTTP:使用普通HTTP协议
  • 事件类型:支持自定义事件类型

# 3.2 基本使用

// 创建EventSource连接
const eventSource = new EventSource('/api/events');
// 或带查询参数
// const eventSource = new EventSource('/api/events?token=abc123');

// 监听消息(默认事件)
eventSource.addEventListener('message', function(event) {
  console.log('收到消息:', event.data);
  
  // 解析JSON
  const data = JSON.parse(event.data);
  console.log(data);
});

// 监听自定义事件
eventSource.addEventListener('notification', function(event) {
  console.log('收到通知:', event.data);
});

eventSource.addEventListener('update', function(event) {
  console.log('收到更新:', event.data);
});

// 连接打开
eventSource.addEventListener('open', function(event) {
  console.log('SSE连接已建立');
});

// 连接错误
eventSource.addEventListener('error', function(event) {
  if (event.target.readyState === EventSource.CLOSED) {
    console.log('SSE连接已关闭');
  } else {
    console.error('SSE连接错误');
  }
});

// 关闭连接
// eventSource.close();

# 3.3 readyState状态

console.log(eventSource.readyState);

// 0: CONNECTING - 正在连接
// 1: OPEN - 连接已建立
// 2: CLOSED - 连接已关闭

// 检查连接状态
function checkSSEState() {
  switch(eventSource.readyState) {
    case EventSource.CONNECTING:
      console.log('正在连接...');
      break;
    case EventSource.OPEN:
      console.log('连接已建立');
      break;
    case EventSource.CLOSED:
      console.log('连接已关闭');
      break;
  }
}

# 3.4 服务端实现(Node.js示例)

// Express服务器端
app.get('/api/events', (req, res) => {
  // 设置SSE响应头
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');
  
  // 发送消息
  const sendEvent = (data, event = 'message', id = null) => {
    if (id) {
      res.write(`id: ${id}\n`);
    }
    if (event !== 'message') {
      res.write(`event: ${event}\n`);
    }
    res.write(`data: ${JSON.stringify(data)}\n\n`);
  };
  
  // 发送初始消息
  sendEvent({ message: '连接成功' });
  
  // 定时发送消息
  const interval = setInterval(() => {
    sendEvent({ 
      time: new Date().toISOString(),
      count: Math.random()
    }, 'update');
  }, 5000);
  
  // 发送自定义事件
  setTimeout(() => {
    sendEvent({ 
      title: '新通知',
      content: '您有一条新消息'
    }, 'notification', Date.now());
  }, 10000);
  
  // 客户端断开连接
  req.on('close', () => {
    clearInterval(interval);
    console.log('客户端断开连接');
  });
});

# 3.5 封装EventSource类

class SSEClient {
  constructor(url, options = {}) {
    this.url = url;
    this.options = {
      reconnect: true,
      reconnectInterval: 3000,
      withCredentials: false,
      ...options
    };
    
    this.eventSource = null;
    this.eventHandlers = new Map();
  }
  
  connect() {
    if (this.eventSource) {
      this.eventSource.close();
    }
    
    this.eventSource = new EventSource(this.url, {
      withCredentials: this.options.withCredentials
    });
    
    // 默认消息事件
    this.eventSource.addEventListener('message', (event) => {
      this.handleEvent('message', event);
    });
    
    // 连接打开
    this.eventSource.addEventListener('open', (event) => {
      console.log('SSE连接成功');
      this.handleEvent('open', event);
    });
    
    // 连接错误
    this.eventSource.addEventListener('error', (event) => {
      console.error('SSE连接错误');
      this.handleEvent('error', event);
      
      if (this.options.reconnect && 
          this.eventSource.readyState === EventSource.CLOSED) {
        this.reconnect();
      }
    });
    
    // 注册已有的事件监听器
    this.eventHandlers.forEach((handlers, eventType) => {
      if (eventType !== 'message' && eventType !== 'open' && eventType !== 'error') {
        this.eventSource.addEventListener(eventType, (event) => {
          this.handleEvent(eventType, event);
        });
      }
    });
  }
  
  reconnect() {
    console.log('尝试重连...');
    setTimeout(() => {
      this.connect();
    }, this.options.reconnectInterval);
  }
  
  on(eventType, handler) {
    if (!this.eventHandlers.has(eventType)) {
      this.eventHandlers.set(eventType, []);
      
      // 为新事件类型添加监听器
      if (this.eventSource && 
          eventType !== 'message' && 
          eventType !== 'open' && 
          eventType !== 'error') {
        this.eventSource.addEventListener(eventType, (event) => {
          this.handleEvent(eventType, event);
        });
      }
    }
    
    this.eventHandlers.get(eventType).push(handler);
  }
  
  off(eventType, handler) {
    if (this.eventHandlers.has(eventType)) {
      const handlers = this.eventHandlers.get(eventType);
      const index = handlers.indexOf(handler);
      
      if (index > -1) {
        handlers.splice(index, 1);
      }
    }
  }
  
  handleEvent(eventType, event) {
    if (this.eventHandlers.has(eventType)) {
      this.eventHandlers.get(eventType).forEach(handler => {
        try {
          handler(event);
        } catch (error) {
          console.error(`事件处理错误 (${eventType}):`, error);
        }
      });
    }
  }
  
  close() {
    if (this.eventSource) {
      this.eventSource.close();
      this.eventSource = null;
    }
  }
}

// 使用示例
const sseClient = new SSEClient('/api/events', {
  reconnect: true,
  reconnectInterval: 3000
});

sseClient.on('open', () => {
  console.log('连接建立');
});

sseClient.on('message', (event) => {
  const data = JSON.parse(event.data);
  console.log('收到消息:', data);
});

sseClient.on('notification', (event) => {
  const data = JSON.parse(event.data);
  console.log('收到通知:', data);
  showNotification(data);
});

sseClient.on('update', (event) => {
  const data = JSON.parse(event.data);
  console.log('收到更新:', data);
  updateUI(data);
});

sseClient.on('error', () => {
  console.error('连接错误');
});

sseClient.connect();

# 四、WebSocket vs SSE对比

# 4.1 对比表格

特性 WebSocket Server-Sent Events
通信方向 双向 单向(服务器→客户端)
协议 WebSocket (ws://, wss://) HTTP/HTTPS
数据格式 文本、二进制 仅文本
自动重连 需手动实现 自动重连
浏览器兼容性 较好(IE10+) 较好(IE不支持)
服务端实现 需要WebSocket服务器 普通HTTP服务器即可
代理支持 可能被阻止 良好
消息ID 需手动实现 内置支持
适用场景 聊天、游戏、协作编辑 通知、实时数据推送、日志流

# 4.2 选择建议

// 使用WebSocket:
// - 需要双向实时通信
// - 需要传输二进制数据
// - 高频率消息交互
// - 实时游戏、聊天应用

// 使用SSE:
// - 只需服务器推送
// - 简单的实时更新
// - 服务器无需特殊支持
// - 实时通知、股票行情、日志监控

# 五、综合示例

<html>
  <div id="realtime-demo-i3j4k">
    <h3>实时通信综合示例</h3>
    
    <div class="tabs-i3j4k">
      <button class="tab-btn-i3j4k active-i3j4k" data-tab="websocket">WebSocket</button>
      <button class="tab-btn-i3j4k" data-tab="sse">Server-Sent Events</button>
      <button class="tab-btn-i3j4k" data-tab="compare">对比</button>
    </div>
    
    <div class="tab-content-i3j4k">
      <div id="websocket-i3j4k" class="panel-i3j4k active-i3j4k">
        <h4>WebSocket模拟</h4>
        <div class="status-i3j4k">
          状态: <span id="ws-status-i3j4k" class="disconnected-i3j4k">未连接</span>
        </div>
        <div class="controls-i3j4k">
          <button id="ws-connect-i3j4k">连接</button>
          <button id="ws-disconnect-i3j4k" disabled>断开</button>
          <input type="text" id="ws-input-i3j4k" placeholder="输入消息">
          <button id="ws-send-i3j4k" disabled>发送</button>
        </div>
        <div class="messages-i3j4k" id="ws-messages-i3j4k"></div>
      </div>
      
      <div id="sse-i3j4k" class="panel-i3j4k">
        <h4>SSE模拟</h4>
        <div class="status-i3j4k">
          状态: <span id="sse-status-i3j4k" class="disconnected-i3j4k">未连接</span>
        </div>
        <div class="controls-i3j4k">
          <button id="sse-connect-i3j4k">连接</button>
          <button id="sse-disconnect-i3j4k" disabled>断开</button>
        </div>
        <div class="messages-i3j4k" id="sse-messages-i3j4k"></div>
      </div>
      
      <div id="compare-i3j4k" class="panel-i3j4k">
        <h4>技术对比</h4>
        <table class="compare-table-i3j4k">
          <thead>
            <tr>
              <th>特性</th>
              <th>WebSocket</th>
              <th>Server-Sent Events</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>通信方向</td>
              <td>双向</td>
              <td>单向(服务器→客户端)</td>
            </tr>
            <tr>
              <td>协议</td>
              <td>WebSocket (ws://, wss://)</td>
              <td>HTTP/HTTPS</td>
            </tr>
            <tr>
              <td>数据格式</td>
              <td>文本、二进制</td>
              <td>仅文本</td>
            </tr>
            <tr>
              <td>自动重连</td>
              <td>需手动实现</td>
              <td>✓ 自动重连</td>
            </tr>
            <tr>
              <td>浏览器兼容</td>
              <td>IE10+</td>
              <td>现代浏览器(IE不支持)</td>
            </tr>
            <tr>
              <td>服务端实现</td>
              <td>需WebSocket服务器</td>
              <td>普通HTTP服务器</td>
            </tr>
            <tr>
              <td>适用场景</td>
              <td>聊天、游戏、协作</td>
              <td>通知、实时数据推送</td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
  </div>
</html>

<style>
  #realtime-demo-i3j4k {
    font-family: sans-serif;
    max-width: 800px;
  }
  
  .tabs-i3j4k {
    display: flex;
    gap: 8px;
    margin-bottom: 16px;
    border-bottom: 2px solid #e0e0e0;
  }
  
  .tab-btn-i3j4k {
    padding: 10px 20px;
    border: none;
    background: transparent;
    cursor: pointer;
    font-size: 14px;
    color: #666;
    border-bottom: 2px solid transparent;
    margin-bottom: -2px;
    transition: all 0.2s;
  }
  
  .tab-btn-i3j4k:hover {
    color: #2196F3;
  }
  
  .tab-btn-i3j4k.active-i3j4k {
    color: #2196F3;
    border-bottom-color: #2196F3;
  }
  
  .panel-i3j4k {
    display: none;
    padding: 20px;
    background: #f9f9f9;
    border-radius: 8px;
  }
  
  .panel-i3j4k.active-i3j4k {
    display: block;
  }
  
  .panel-i3j4k h4 {
    margin: 0 0 16px 0;
    color: #333;
  }
  
  .status-i3j4k {
    padding: 12px;
    background: white;
    border-radius: 4px;
    margin-bottom: 12px;
    font-size: 14px;
  }
  
  .status-i3j4k span {
    font-weight: bold;
  }
  
  .disconnected-i3j4k {
    color: #f44336;
  }
  
  .connected-i3j4k {
    color: #4caf50;
  }
  
  .connecting-i3j4k {
    color: #ff9800;
  }
  
  .controls-i3j4k {
    display: flex;
    gap: 8px;
    margin-bottom: 16px;
    flex-wrap: wrap;
  }
  
  .controls-i3j4k button {
    padding: 8px 16px;
    background: #2196F3;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 14px;
    transition: background 0.2s;
  }
  
  .controls-i3j4k button:hover:not(:disabled) {
    background: #1976D2;
  }
  
  .controls-i3j4k button:disabled {
    background: #ccc;
    cursor: not-allowed;
  }
  
  .controls-i3j4k input {
    flex: 1;
    min-width: 200px;
    padding: 8px 12px;
    border: 1px solid #ddd;
    border-radius: 4px;
    font-size: 14px;
  }
  
  .messages-i3j4k {
    background: white;
    border: 1px solid #e0e0e0;
    border-radius: 4px;
    padding: 12px;
    max-height: 300px;
    overflow-y: auto;
    font-size: 13px;
  }
  
  .message-i3j4k {
    padding: 8px;
    margin-bottom: 8px;
    border-radius: 4px;
    border-left: 3px solid #2196F3;
    background: #f5f5f5;
  }
  
  .message-i3j4k .time-i3j4k {
    color: #999;
    font-size: 11px;
    margin-right: 8px;
  }
  
  .message-i3j4k.sent-i3j4k {
    border-left-color: #4caf50;
    background: #e8f5e9;
  }
  
  .message-i3j4k.received-i3j4k {
    border-left-color: #2196F3;
    background: #e3f2fd;
  }
  
  .message-i3j4k.system-i3j4k {
    border-left-color: #ff9800;
    background: #fff3e0;
  }
  
  .compare-table-i3j4k {
    width: 100%;
    border-collapse: collapse;
    background: white;
  }
  
  .compare-table-i3j4k th,
  .compare-table-i3j4k td {
    padding: 12px;
    text-align: left;
    border: 1px solid #e0e0e0;
    font-size: 13px;
  }
  
  .compare-table-i3j4k th {
    background: #f5f5f5;
    font-weight: bold;
    color: #333;
  }
  
  .compare-table-i3j4k tbody tr:hover {
    background: #f9f9f9;
  }
</style>

<script>
  const tabs = document.querySelectorAll('.tab-btn-i3j4k');
  const panels = document.querySelectorAll('.panel-i3j4k');
  
  tabs.forEach(tab => {
    tab.addEventListener('click', () => {
      const targetTab = tab.dataset.tab;
      
      tabs.forEach(t => t.classList.remove('active-i3j4k'));
      panels.forEach(p => p.classList.remove('active-i3j4k'));
      
      tab.classList.add('active-i3j4k');
      document.getElementById(`${targetTab}-i3j4k`).classList.add('active-i3j4k');
    });
  });

  let wsConnected = false;
  let wsInterval = null;
  
  function addWSMessage(type, text) {
    const messagesDiv = document.getElementById('ws-messages-i3j4k');
    const msg = document.createElement('div');
    msg.className = `message-i3j4k ${type}-i3j4k`;
    
    const time = new Date().toLocaleTimeString();
    msg.innerHTML = `<span class="time-i3j4k">${time}</span>${text}`;
    
    messagesDiv.appendChild(msg);
    messagesDiv.scrollTop = messagesDiv.scrollHeight;
  }
  
  document.getElementById('ws-connect-i3j4k').addEventListener('click', () => {
    if (wsConnected) return;
    
    const status = document.getElementById('ws-status-i3j4k');
    status.textContent = '连接中...';
    status.className = 'connecting-i3j4k';
    
    setTimeout(() => {
      wsConnected = true;
      status.textContent = '已连接';
      status.className = 'connected-i3j4k';
      
      document.getElementById('ws-connect-i3j4k').disabled = true;
      document.getElementById('ws-disconnect-i3j4k').disabled = false;
      document.getElementById('ws-send-i3j4k').disabled = false;
      
      addWSMessage('system', 'WebSocket连接已建立(模拟)');
      
      wsInterval = setInterval(() => {
        const messages = [
          '服务器消息: 当前在线用户 123 人',
          '服务器消息: 收到新订单通知',
          '服务器消息: 系统将在5分钟后维护',
          '服务器消息: 数据更新完成'
        ];
        
        const randomMsg = messages[Math.floor(Math.random() * messages.length)];
        addWSMessage('received', randomMsg);
      }, 8000);
    }, 500);
  });
  
  document.getElementById('ws-disconnect-i3j4k').addEventListener('click', () => {
    if (!wsConnected) return;
    
    wsConnected = false;
    clearInterval(wsInterval);
    
    const status = document.getElementById('ws-status-i3j4k');
    status.textContent = '已断开';
    status.className = 'disconnected-i3j4k';
    
    document.getElementById('ws-connect-i3j4k').disabled = false;
    document.getElementById('ws-disconnect-i3j4k').disabled = true;
    document.getElementById('ws-send-i3j4k').disabled = true;
    
    addWSMessage('system', 'WebSocket连接已关闭');
  });
  
  document.getElementById('ws-send-i3j4k').addEventListener('click', () => {
    const input = document.getElementById('ws-input-i3j4k');
    const message = input.value.trim();
    
    if (message && wsConnected) {
      addWSMessage('sent', `发送: ${message}`);
      input.value = '';
      
      setTimeout(() => {
        addWSMessage('received', `服务器回复: 收到消息 "${message}"`);
      }, 1000);
    }
  });
  
  document.getElementById('ws-input-i3j4k').addEventListener('keypress', (e) => {
    if (e.key === 'Enter') {
      document.getElementById('ws-send-i3j4k').click();
    }
  });

  let sseConnected = false;
  let sseInterval = null;
  
  function addSSEMessage(type, text) {
    const messagesDiv = document.getElementById('sse-messages-i3j4k');
    const msg = document.createElement('div');
    msg.className = `message-i3j4k ${type}-i3j4k`;
    
    const time = new Date().toLocaleTimeString();
    msg.innerHTML = `<span class="time-i3j4k">${time}</span>${text}`;
    
    messagesDiv.appendChild(msg);
    messagesDiv.scrollTop = messagesDiv.scrollHeight;
  }
  
  document.getElementById('sse-connect-i3j4k').addEventListener('click', () => {
    if (sseConnected) return;
    
    const status = document.getElementById('sse-status-i3j4k');
    status.textContent = '连接中...';
    status.className = 'connecting-i3j4k';
    
    setTimeout(() => {
      sseConnected = true;
      status.textContent = '已连接';
      status.className = 'connected-i3j4k';
      
      document.getElementById('sse-connect-i3j4k').disabled = true;
      document.getElementById('sse-disconnect-i3j4k').disabled = false;
      
      addSSEMessage('system', 'SSE连接已建立(模拟)');
      
      sseInterval = setInterval(() => {
        const events = [
          { type: 'message', text: '默认消息: 实时数据更新' },
          { type: 'received', text: '通知事件: 您有新的消息' },
          { type: 'received', text: '更新事件: 股票价格变动 +2.5%' },
          { type: 'received', text: '系统事件: 定时任务执行完成' }
        ];
        
        const randomEvent = events[Math.floor(Math.random() * events.length)];
        addSSEMessage(randomEvent.type, randomEvent.text);
      }, 5000);
    }, 500);
  });
  
  document.getElementById('sse-disconnect-i3j4k').addEventListener('click', () => {
    if (!sseConnected) return;
    
    sseConnected = false;
    clearInterval(sseInterval);
    
    const status = document.getElementById('sse-status-i3j4k');
    status.textContent = '已断开';
    status.className = 'disconnected-i3j4k';
    
    document.getElementById('sse-connect-i3j4k').disabled = false;
    document.getElementById('sse-disconnect-i3j4k').disabled = true;
    
    addSSEMessage('system', 'SSE连接已关闭');
  });
</script>

# 六、最佳实践

# 6.1 WebSocket最佳实践

  1. 实现自动重连:网络不稳定时自动重新连接
  2. 心跳检测:定期发送心跳消息保持连接
  3. 消息队列:连接断开时缓存消息
  4. 错误处理:捕获并处理所有错误
  5. 资源清理:组件销毁时关闭连接

# 6.2 SSE最佳实践

  1. 利用自动重连:SSE自带重连机制
  2. 设置合理的重试时间:服务端通过retry字段控制
  3. 使用事件类型:区分不同类型的消息
  4. 处理错误状态:监听error事件
  5. 正确关闭连接:不需要时调用close()

# 6.3 通用最佳实践

  1. 使用HTTPS/WSS:加密传输保证安全
  2. 身份验证:通过Token或Cookie验证身份
  3. 消息压缩:大数据量时使用压缩
  4. 限流控制:避免消息过载
  5. 监控和日志:记录连接状态和错误

# 七、总结

实时通信是现代Web应用的关键技术:

  1. WebSocket:全双工通信,适合聊天、游戏、协作
  2. SSE:单向推送,适合通知、实时数据、日志流
  3. 选择建议:根据需求选择合适的方案
  4. 最佳实践:重连、心跳、错误处理、资源清理

掌握实时通信技术,能够构建高效、实时的交互应用。

祝你变得更强!

编辑 (opens new window)
#Web API#WebSocket#SSE#实时通信
上次更新: 2025/11/28
Web API网络-HTTP请求详解
Web API交互-用户体验增强

← Web API网络-HTTP请求详解 Web API交互-用户体验增强→

最近更新
01
AI编程时代的一些心得
09-11
02
Claude Code与Codex的协同工作
09-01
03
Claude Code 最佳实践(个人版)
08-01
更多文章>
Theme by Vdoing | Copyright © 2018-2025 京ICP备2021021832号-2 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式