实时通信技术开发经历

一. EventSource (Server-Sent Events)

1. 特点

  • 协议:基于HTTP/HTTPS,单向通信(服务端 → 客户端)
  • 数据格式:仅支持UTF-8文本,即text/event-stream
  • 自动重连:内置断连重试机制,默认超时时间为45s
  • 轻量级:原生浏览器API,无需额外库

2. 限制

  • 单向通信:客户端无法通过EventSource向服务端发送数据,例如在流式输出过程中,客户端无法通过EventSource告诉服务端中断
  • 无二进制支持:仅支持文本数据,无法传输二进制(如图片、音频)
  • HTTP/1.1限制:浏览器对同一域名下的并发连接数有限制(Chrome为6个)
  • 无心跳检测:依赖HTTP层断连检测,可能因代理/Nginx超时设置失效
  • 不支持自定义请求头:若需要支持网关,EventSource无法支持配置请求头

3. 适用场景

  • 实时通知(如股票价格、新闻推送)
  • 服务端日志流式输出
  • 兼容性要求高的简单实时场景(不支持WebSocket的旧环境)

4. 隐患&改进建议

  • 连接稳定性问题:依赖浏览器默认重连机制,但未显式设置 Retry-After(服务端控制重试间隔)
  • 数据完整性风险:直接拼接 lastChat.content + response.data.content,若消息乱序或丢失分片,会导致内容错乱
  • 内存泄漏风险:未在组件卸载时清理 EventSource,可能导致重复连接

5. ⚠️开发需注意

事件类型过滤

  • 方法:addEventListener 监听特定事件类型(非默认 message)
  • 用途:区分不同业务事件(如 status、data)
javascript 复制代码
eventSource.addEventListener('status', (e) => {
  console.log('Status update:', e.data);
});

自定义重试逻辑

  • 特性:通过 Retry-After 响应头控制重试间隔(需服务端支持)(不推荐)
http 复制代码
HTTP/1.1 503 Service Unavailable
Retry-After: 10  # 10秒后重试
  • 一旦eventsource走到onerror回调中,就需要考虑断线重连问题,默认情况是eventsource会自动重连,这并不能够达到预期效果,因为前端无法感知eventsource的非预期自动重连(相当于重新发起请求,多次请求返回的数据也会以拼接的方式展示)。所以,前端需要手动重连,走到onerror逻辑就close掉eventsource,然后自定义重试逻辑(最多重试三次为佳)。重试逻辑需要考虑从上一个message的messageId+1开始,以便内容拼接正确。

连接状态检测

  • 属性:readyState(CONNECTING=0, OPEN=1, CLOSED=2)
javascript 复制代码
if (eventSource.readyState === EventSource.OPEN) {
  console.log('Connection active');
}

消息ID跟踪

  • 特性:服务端可通过 id: 字段发送消息 ID,客户端断连后通过 Last-Event-ID 请求恢复

心跳检测

  • 用途:eventsource默认超时时间45s,但是在本地和在服务器超时时间可能不一致,若超时,前端会直接close掉eventsource,但是不会导致服务端close,单向通信就是这样,所以当服务端在等待其他服务的数据时,也需要吐数据给前端,保证前端的eventsource的连接 -立即生效的情况 根据MDN文档,当调用EventSource.close()方法时,在以下情况下会立即真正关闭连接:

  • ✅ 连接处于开启状态时(readyState = EventSource.OPEN = 1)

  • ✅ 连接处于连接中状态时(readyState = EventSource.CONNECTING = 0)

  • 调用close()后,浏览器会: (1) 立即关闭与服务器的HTTP连接 (2) 将readyState属性设置为EventSource.CLOSED(值为2) (3) 停止自动重连机制

  • 不会产生任何效果的情况 (1) ❌ 连接已经关闭时(readyState = EventSource.CLOSED = 2) (2) 如果连接已经关闭,该方法不会做任何事情

二. WebSocket

1. 特点

  • 协议:基于TCP的独立协议,通过HTTP握手升级
  • 双向通信:客户端和服务端可同时发送/接收数据
  • 低延迟:无HTTP头开销,数据帧直接传输
  • 数据支持:支持文本、二进制(ArrayBuffer、Blob)
  • 扩展性:支持自定义子协议和压缩扩展

2. 限制

  • 连接管理复杂:需手动处理连接状态、重连逻辑
  • 代理穿透问题:部分代理/防火墙不支持WebSocket协议
  • 资源占用:长连接占用服务器socket资源
  • 无缓存机制:不支持HTTP缓存,需自行实现离线处理
  • 状态同步难:客户端/服务端状态不一致时处理复杂

3. 适用场景

  • 即时聊天、在线客服系统
  • 实时协作编辑(Google Docs类应用)
  • 在线游戏、实时竞技
  • 金融交易、股票行情实时推送
  • 物联网设备控制和监控

4. 隐患&改进建议

  • 频繁断连问题:移动网络切换、信号弱时连接不稳定,需实现指数退避重连
  • 消息丢失风险:网络中断时未确认消息可能丢失,需要消息确认机制
  • 内存泄漏:事件监听器未正确移除,WebSocket对象未释放
  • 安全风险:缺少认证续期机制,长连接中token过期处理

5. 开发经验

心跳检测

🚀🚀 主要作用

  • 连接存活检测:确保WebSocket连接真正可用,而不是"僵尸连接"

  • 网络质量监控:通过ping-pong的往返时间评估网络延迟

  • 代理超时防护:防止代理服务器/负载均衡器因长时间无数据而断开连接

🔥🔥 解决的实际问题

  • 移动端网络切换:4G/WiFi切换时连接断开但客户端不知情

  • 企业防火墙:长时间无数据的连接被防火墙清理

  • 云服务负载均衡:ALB/ELB的idle timeout导致连接被回收

三. event-source-polyfill

1. 特点

  • 兼容性扩展:为不支持EventSource的浏览器提供polyfill
  • 功能增强:支持自定义请求头、CORS凭证
  • API一致:与原生EventSource API完全兼容
  • 渐进增强:优先使用原生实现,回退到polyfill
  • 轻量级:约10KB,不显著增加包体积

2. 限制

  • 性能差异:polyfill基于XMLHttpRequest,性能略低于原生
  • 兼容性测试负担:需在目标浏览器中充分测试
  • 功能子集:某些边缘特性可能无法完全模拟
  • 维护成本:需要跟进polyfill版本更新
  • 调试复杂性:polyfill实现可能影响错误堆栈

3. 适用场景

  • 企业内网环境(IE11等旧版浏览器)
  • 需要自定义请求头的SSE场景
  • 跨域SSE请求(需要凭证)
  • 渐进式Web应用的兼容性方案
  • 微信小程序等环境的SSE支持

4. 隐患&改进建议

  • 特性检测失效:部分环境下原生检测可能不准确,建议强制使用polyfill
  • 内存泄漏:polyfill的XMLHttpRequest未正确清理
  • 事件时序差异:polyfill的事件触发时机可能与原生不同
  • 错误处理不一致:网络错误的处理逻辑可能不同

5. 开发经验

自定义请求头支持

javascript 复制代码
import EventSource from 'event-source-polyfill';

const eventSource = new EventSource('/stream', {
  headers: {
    'Authorization': 'Bearer ' + token,
    'X-Custom-Header': 'value'
  },
  withCredentials: true
});

特性检测与降级

javascript 复制代码
function createEventSource(url, options = {}) {
  // 检测原生支持
  const hasNativeSupport = typeof window.EventSource !== 'undefined';
  
  if (hasNativeSupport && !options.headers) {
    return new window.EventSource(url);
  } else {
    return new EventSourcePolyfill(url, options);
  }
}

错误重试增强

javascript 复制代码
class RobustEventSource {
  constructor(url, options = {}) {
    this.url = url;
    this.options = options;
    this.retryCount = 0;
    this.maxRetries = options.maxRetries || 5;
    this.retryDelay = options.retryDelay || 1000;
    this.connect();
  }
  
  connect() {
    this.eventSource = new EventSourcePolyfill(this.url, this.options);
    
    this.eventSource.onerror = (e) => {
      if (this.retryCount < this.maxRetries) {
        setTimeout(() => {
          this.retryCount++;
          this.connect();
        }, this.retryDelay * Math.pow(2, this.retryCount));
      }
    };
  }
}

四. socket.io-client

1. 特点

  • 传输降级:WebSocket → 长轮询 → 短轮询的自动降级
  • 房间管理:内置房间(room)和命名空间(namespace)概念
  • 自动重连:智能重连策略,支持指数退避
  • 事件系统:基于事件的发布订阅模式
  • 跨平台:支持浏览器、Node.js、React Native等环境
  • 中间件支持:支持连接中间件和认证

2. 限制

  • 体积较大:压缩后约60KB,显著大于原生WebSocket
  • 版本兼容:客户端与服务端版本需要兼容
  • 学习曲线:需要理解Socket.IO特有概念和API
  • 调试复杂:多层抽象增加问题定位难度
  • 服务端绑定:必须使用Socket.IO服务端,无法与标准WebSocket服务端通信

3. 适用场景

  • 大型实时应用(聊天室、协作平台)
  • 需要房间管理的多用户应用
  • 对网络环境要求不高的企业应用
  • 快速原型开发和MVP验证
  • 需要跨平台统一实时通信的项目

4. 隐患&改进建议

  • 连接开销过大:初始化时间长,需要优化连接参数
  • 内存占用高:事件监听器过多,需要及时清理
  • 网络策略冲突:企业网络可能阻止长轮询,需要配置传输方式
  • 调试信息过多:生产环境需要关闭debug模式

5. 开发经验

连接配置优化

javascript 复制代码
import io from 'socket.io-client';

const socket = io('http://localhost:3000', {
  // 传输方式优先级
  transports: ['websocket', 'polling'],
  // 连接超时
  timeout: 20000,
  // 重连配置
  reconnection: true,
  reconnectionAttempts: 5,
  reconnectionDelay: 1000,
  reconnectionDelayMax: 5000,
  // 随机化重连延迟
  randomizationFactor: 0.5,
  // 强制使用新连接
  forceNew: false
});

房间和命名空间

javascript 复制代码
// 命名空间
const adminSocket = io('/admin');
const userSocket = io('/user');

// 房间操作
socket.emit('join-room', 'room1');
socket.on('room-message', (data) => {
  console.log('Room message:', data);
});

// 私聊
socket.emit('private-message', {
  to: 'userId',
  message: 'Hello'
});

中间件和认证

javascript 复制代码
// 连接中间件
socket.on('connect', () => {
  socket.emit('authenticate', { token: getAuthToken() });
});

socket.on('authenticated', () => {
  console.log('认证成功');
});

socket.on('unauthorized', () => {
  console.log('认证失败,重新登录');
});

性能监控

javascript 复制代码
// 连接质量监控
socket.on('ping', () => {
  socket.emit('pong', Date.now());
});

// 延迟监控
const startTime = Date.now();
socket.emit('ping-test', startTime, (ackTime) => {
  const latency = Date.now() - ackTime;
  console.log('延迟:', latency + 'ms');
});

六. fetch-event-source

1. 特点

  • 基于Fetch API:使用现代 fetch API 实现,支持更灵活的请求配置
  • 支持POST请求:可以通过POST发送请求体,突破GET参数长度限制
  • 自定义请求头:完全支持自定义请求头,便于认证和网关配置
  • 更好的错误处理:提供更详细的错误信息和状态码处理
  • TypeScript支持:原生TypeScript支持,提供完整的类型定义
  • 取消机制:支持 AbortController 取消请求

2. 限制

  • 需要额外依赖:不是浏览器原生API,需要安装第三方库
  • 兼容性要求:依赖 fetch API 和 ReadableStream,需要现代浏览器
  • 调试复杂度:相比原生EventSource,调试和错误追踪更复杂
  • 性能开销:额外的抽象层可能带来轻微性能开销
  • 社区生态:相对较新,社区资源和案例较少

3. 适用场景

  • 需要POST请求发送复杂参数的SSE场景
  • 需要自定义请求头进行认证的企业应用
  • 需要更精细错误处理的生产环境
  • 现代前端框架中的SSE集成
  • 需要取消机制的交互式应用

4. 隐患&改进建议

  • 内存泄漏风险:未正确调用 AbortController.abort() 可能导致连接无法释放
  • 错误重试复杂:需要手动实现重连逻辑,容易出现指数爆炸或无限重试
  • 流解析错误:手动解析SSE流时可能出现数据截断或格式错误
  • 并发控制缺失:没有内置连接数限制,可能创建过多并发连接

5. 开发经验

基本使用

javascript 复制代码
import { fetchEventSource } from '@microsoft/fetch-event-source';

await fetchEventSource('/api/stream', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer ' + token,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    query: 'complex query data',
    options: { temperature: 0.7 }
  }),
  onmessage(event) {
    console.log('收到消息:', event.data);
  },
  onerror(err) {
    console.error('连接错误:', err);
  }
});

自定义重连逻辑

javascript 复制代码
class RetryableEventSource {
  constructor(url, options = {}) {
    this.url = url;
    this.options = options;
    this.retryCount = 0;
    this.maxRetries = options.maxRetries || 3;
    this.retryDelay = options.retryDelay || 1000;
    this.abortController = new AbortController();
  }

  async connect() {
    try {
      await fetchEventSource(this.url, {
        ...this.options,
        signal: this.abortController.signal,
        
        onmessage: (event) => {
          this.retryCount = 0; // 重置重试计数
          this.options.onmessage?.(event);
        },
        
        onerror: (err) => {
          if (this.retryCount < this.maxRetries) {
            const delay = this.retryDelay * Math.pow(2, this.retryCount);
            setTimeout(() => {
              this.retryCount++;
              this.connect();
            }, delay);
          } else {
            this.options.onerror?.(err);
          }
          
          // 返回重试间隔,阻止默认重连
          return this.retryDelay * Math.pow(2, this.retryCount);
        }
      });
    } catch (error) {
      if (!this.abortController.signal.aborted) {
        console.error('连接失败:', error);
      }
    }
  }

  disconnect() {
    this.abortController.abort();
  }
}

六. 技术选择指南

1. EventSource 适用场景

  • ✅ 简单的单向数据流
  • 🟢 实时通知、日志输出
  • 🟢 对兼容性要求不高的场景

2. fetch-event-source 适用场景

  • ✅ 需要POST请求体的SSE
  • ✅ 需要认证头的企业应用
  • 🟢 现代框架的SSE集成
  • 🟢 需要精细错误控制

3. WebSocket 适用场景

  • ✅ 需要双向通信
  • 🟢 即时聊天、实时游戏
  • 🟢 对延迟敏感的应用

4. event-source-polyfill 适用场景

  • ✅ 需要自定义请求头头的SSE
  • 🟢 需要支持旧浏览器
  • 🟢 企业内网环境

5. socket.io-client 适用场景

  • ✅ 需要房间管理
  • 🟢 大型实时应用
  • 🟢 复杂的企业级项目
相关推荐
星月心城10 分钟前
JS深入之从原型到原型链
前端·javascript
MessiGo11 分钟前
Javascript 编程基础(5)面向对象 | 5.2、原型系统
开发语言·javascript·原型模式
你的人类朋友43 分钟前
🤔Token 存储方案有哪些
前端·javascript·后端
烛阴44 分钟前
从零开始:使用Node.js和Cheerio进行轻量级网页数据提取
前端·javascript·后端
liuyang___1 小时前
日期的数据格式转换
前端·后端·学习·node.js·node
西哥写代码1 小时前
基于cornerstone3D的dicom影像浏览器 第三十一章 从PACS服务加载图像
javascript·pacs·dicom
贩卖纯净水.2 小时前
webpack其余配置
前端·webpack·node.js
码上奶茶2 小时前
HTML 列表、表格、表单
前端·html·表格·标签·列表·文本·表单
抹茶san2 小时前
和 Trae 一起开发可视化拖拽编辑项目(1) :迈出第一步
前端·trae