一文讲透 Server-Sent Events (SSE):从原理到实践的完整指南

239 阅读7分钟

mp.weixin.qq.com/s/z-JbC8yCB…

github.com/Azure/fetch…

引言

在当今的 Web 应用开发中,实时通信已成为提升用户体验的关键要素。传统的 HTTP 请求-响应模式虽然简单可靠,但在处理实时数据更新时却显得力不从心。正是在这样的背景下,Server-Sent Events (SSE) 应运而生,为服务器向客户端的单向实时通信提供了优雅的解决方案。

想象一下这样的场景:股票行情实时更新、新闻推送、社交媒体通知、文件处理进度条——所有这些都需要服务器能够主动向客户端推送数据。SSE 正是为此而生,它基于标准的 HTTP 协议,提供了简单而强大的服务器推送能力。

什么是 SSE?

Server-Sent Events (SSE) 是一种基于 HTTP 的服务器推送技术,允许服务器主动向客户端发送数据。与 WebSocket 的双向通信不同,SSE 专注于单向的服务器到客户端通信,这种设计理念使得它在许多场景下更加简单和高效。

核心特征

  • 单向通信:服务器 → 客户端
  • 基于 HTTP:无需特殊协议或端口
  • 自动重连:内置连接恢复机制
  • 简单 API:浏览器原生支持,API 简洁易用
  • 文本协议:人类可读的消息格式

与相关技术对比

技术通信方向协议复杂度适用场景
SSE服务器→客户端HTTP实时通知、数据流
WebSocket双向WS聊天、游戏、协作编辑
长轮询客户端→服务器HTTP兼容性要求高的场景
短轮询客户端→服务器HTTP实时性要求不高的场景

SSE 协议深度解析

协议基础

SSE 协议建立在标准的 HTTP 之上,使用 text/event-stream 内容类型。客户端通过创建 EventSource 对象建立连接,服务器通过保持 HTTP 连接开放来持续发送数据。

消息格式规范

SSE 消息由一系列键值对组成,每个字段以换行符结束,消息以双换行符结束。

基本语法:

field: value\n
\n

完整示例:

// 单条消息
event: message
data: 这是消息内容
id: 12345
retry: 5000

// 另一条消息
data: 这是另一条消息
data: 多行消息的第二行

字段详解

data 字段(必需)

定义消息的实际内容。支持多行数据。

// 单行数据
data: Hello World

// 多行数据
data: 第一行内容
data: 第二行内容
// 客户端接收: "第一行内容\n第二行内容"

event 字段(可选)

指定事件类型,默认为 message

// 默认事件
event: message
data: 普通消息

// 自定义事件
event: userUpdate
data: {"userId"123"name""张三"}

id 字段(可选)

设置消息 ID,用于断线重连时恢复。

id: 1689325200000
data: 重要消息内容

retry 字段(可选)

指定重连延迟时间(毫秒)。

retry: 3000
data: 设置重连时间为3秒

协议高级特性

注释消息

服务器可以发送注释行(以冒号开头),用于保持连接活跃或调试。

: 这是一个注释,用于保持连接
: 心跳检测
data: 实际数据

多事件类型

支持多种事件类型,客户端可以分别处理。

// 股票更新事件
event: stockUpdate
data: {"symbol""AAPL""price"182.3}

// 系统通知事件
event: notification  
data: "系统维护将在1小时后开始"

客户端实现详解

基础 API 使用

创建连接

// 创建 EventSource 实例
const eventSource = new EventSource('/api/events');

// 带认证的URL(注意:EventSource 不支持自定义头部)
const eventSource = new EventSource('/api/events?token=' + encodeURIComponent(token));

// 跨域请求需要显式设置凭据
const eventSource = new EventSource('/api/events', {
    withCredentialstrue  // 发送cookies等凭据
});

事件监听

// 监听默认消息事件
eventSource.onmessagefunction(event) {
    console.log('收到消息:', event.data);
    console.log('最后事件ID:', event.lastEventId);
};

// 监听自定义事件
eventSource.addEventListener('stockUpdate'function(event) {
    const data = JSON.parse(event.data);
    updateStockPrice(data);
});

// 监听连接打开事件
eventSource.onopenfunction(event) {
    console.log('连接已建立');
};

// 监听错误事件
eventSource.onerrorfunction(event) {
    console.error('连接错误:', event);
};

高级客户端实现

完整的 SSE 客户端类

class AdvancedSSEClient {
  constructor(url, options = {}) {
    this.url = url;
    this.options = {
      withCredentials: false,
      autoReconnect: true,
      maxRetries: 5,
      retryDelay: 1000,
      backoffMultiplier: 1.5,
      ...options
    };

    this.eventSource = null;
    this.retryCount = 0;
    this.isConnected = false;
    this.eventHandlers = new Map();
    this.lastEventId = null;
    this.heartbeatInterval = null;
  }

  // 建立连接
  connect() {
    try {
      // 清理现有连接
      if (this.eventSource) {
        this.eventSource.close();
      }

      let url = this.url;
      if (this.lastEventId) {
        const separator = url.includes('?') ? '&' : '?';
        url += `${separator}lastEventId=${encodeURIComponent(this.lastEventId)}`;
      }

      this.eventSource = new EventSource(url, {
        withCredentials: this.options.withCredentials
      });

      this.setupEventHandlers();
      this.startHeartbeatMonitor();

    } catch (error) {
      console.error('创建SSE连接失败:', error);
      this.handleReconnection();
    }
  }

  // 设置事件处理器
  setupEventHandlers() {
    this.eventSource.onopen = () => {
      this.isConnected = true;
      this.retryCount = 0;
      console.log(`${this.url} SSE连接已建立`);
      this.emit('connected');
    };

    this.eventSource.onmessage = (event) => {
      this.lastEventId = event.lastEventId;
      this.emit('message', {
        data: event.data,
        id: event.lastEventId,
        timestamp: Date.now()
      });
    };

    this.eventSource.onerror = (event) => {
      this.isConnected = false;
      console.error('SSE连接错误:', event);
      this.emit('error', {
        type: 'connection_error',
        event: event,
        retryCount: this.retryCount
      });

      if (this.options.autoReconnect) {
        this.handleReconnection();
      }
    };

    // 动态添加自定义事件监听器
    for (const [eventName] of this.eventHandlers) {
      if (eventName !== 'message' && eventName !== 'error' && eventName !== 'connected') {
        this.eventSource.addEventListener(eventName, (event) => {
          this.lastEventId = event.lastEventId;
          this.emit(eventName, {
            data: event.data,
            id: event.lastEventId,
            timestamp: Date.now()
          });
        });
      }
    }
  }

  // 心跳监控
  startHeartbeatMonitor() {
    this.heartbeatInterval = setInterval(() => {
      if (this.isConnected) {
        this.emit('heartbeat', { timestamp: Date.now() });
      }
    }, 30000);
  }

  // 添加事件监听器
  on(eventName, handler) {
    if (!this.eventHandlers.has(eventName)) {
      this.eventHandlers.set(eventName, []);
    }
    this.eventHandlers.get(eventName).push(handler);

    // 如果已经连接,为自定义事件添加原生监听器
    if (this.eventSource && this.isConnected &&
      eventName !== 'message' && eventName !== 'error' && eventName !== 'connected') {
      this.eventSource.addEventListener(eventName, (event) => {
        this.emit(eventName, {
          data: event.data,
          id: event.lastEventId,
          timestamp: Date.now()
        });
      });
    }
  }

  // 触发事件
  emit(eventName, data) {
    const handlers = this.eventHandlers.get(eventName) || [];
    handlers.forEach(handler => {
      try {
        handler(data);
      } catch (error) {
        console.error(`事件处理器错误 (${eventName}):`, error);
      }
    });
  }

  // 处理重连逻辑
  handleReconnection() {
    if (this.retryCount >= this.options.maxRetries) {
      console.error('达到最大重连次数,停止重连');
      this.emit('error', {
        type: 'max_retries_exceeded',
        retryCount: this.retryCount
      });
      return;
    }

    this.retryCount++;
    const baseDelay = this.options.retryDelay;
    const backoffDelay = baseDelay * Math.pow(this.options.backoffMultiplier, this.retryCount - 1);
    const maxDelay = 30000; // 最大延迟30秒
    const actualDelay = Math.min(backoffDelay, maxDelay);

    console.log(`${actualDelay}ms后尝试第${this.retryCount}次重连...`);

    setTimeout(() => {
      this.connect();
    }, actualDelay);
  }

  // 关闭连接
  close() {
    if (this.heartbeatInterval) {
      clearInterval(this.heartbeatInterval);
      this.heartbeatInterval = null;
    }

    if (this.eventSource) {
      this.eventSource.close();
      this.isConnected = false;
      this.emit('disconnected');
    }
  }

  // 资源清理
  destroy() {
    this.close();
    this.eventHandlers.clear();
  }
}

服务器端实现

Node.js (Express) 实现

生产级实现

import express from 'express';

const app = express();

// 连接管理器
class ConnectionManager {
  constructor() {
    this.clients = new Map();
    this.heartbeatInterval = null;
    this.startHeartbeat();
  }

  // 添加客户端
  addClient(req, res) {
    const clientId = this.generateClientId();

    // 设置SSE响应头
    res.writeHead(200, {
      'Content-Type': 'text/event-stream; charset=utf-8',
      'Cache-Control': 'no-cache, no-transform',
      'Connection': 'keep-alive',
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Headers': 'Cache-Control',
      'X-Accel-Buffering': 'no'// 禁用Nginx缓冲
    });

    const client = {
      id: clientId,
      response: res,
      ip: req.ip,
      connectedAt: new Date(),
      lastActivity: new Date()
    };

    this.clients.set(clientId, client);

    // 处理Last-Event-ID头部
    const lastEventId = req.headers['last-event-id'];
    if (lastEventId) {
      this.sendToClient(client, 'catchup', {
        message: '恢复连接',
        lastEventId: lastEventId,
        timestamp: new Date().toISOString()
      });
    }

    // 发送连接确认
    this.sendToClient(client, 'connected', {
      clientId: clientId,
      message: '连接成功',
      timestamp: new Date().toISOString()
    });

    console.log(`客户端 ${clientId} 已连接,总连接数: ${this.clients.size}`);

    // 客户端断开连接处理
    req.on('close', () => {
      this.removeClient(clientId);
    });

    req.on('error', (error) => {
      console.error(`客户端 ${clientId} 连接错误:`, error);
      this.removeClient(clientId);
    });

    return clientId;
  }

  // 发送消息到客户端
  sendToClient(client, event, data, id = null) {
    try {
      if (!client.response.writable) {
        this.removeClient(client.id);
        return false;
      }

      const message = [];

      if (event && event !== 'message') {
        message.push(`event: ${event}`);
      }

      // 数据验证和序列化
      const validatedData = this.validateMessageData(data);
      message.push(`data: ${validatedData}`);

      const messageId = id || Date.now().toString();
      message.push(`id: ${messageId}`);

      // 添加时间戳用于调试
      message.push(`: timestamp: ${new Date().toISOString()}`);

      message.push('', ''); // 空行表示消息结束

      client.response.write(message.join('\n'));
      client.lastActivity = new Date();

      // 尝试刷新缓冲区
      if (typeof client.response.flush === 'function') {
        client.response.flush();
      }

      return true;
    } catch (error) {
      console.error(`向客户端 ${client.id} 发送消息失败:`, error);
      this.removeClient(client.id);
      return false;
    }
  }

  // 数据验证
  validateMessageData(data) {
    if (typeof data === 'object') {
      const jsonString = JSON.stringify(data);
      // 防止过大的消息
      if (jsonString.length > 10000) {
        thrownewError('Message too large');
      }
      return jsonString;
    } else if (typeof data === 'string') {
      // 防止注入攻击
      if (data.includes('\0') || data.length > 10000) {
        thrownewError('Invalid message content');
      }
      return data;
    } else {
      returnString(data);
    }
  }

  // 广播消息
  broadcast(event, data, excludeClientId = null) {
    const messageId = Date.now().toString();
    let successCount = 0;
    let failCount = 0;

    this.clients.forEach((client, clientId) => {
      if (clientId !== excludeClientId) {
        const success = this.sendToClient(client, event, data, messageId);
        if (success) {
          successCount++;
        } else {
          failCount++;
        }
      }
    });

    console.log(`广播消息 ${event}: 成功 ${successCount}, 失败 ${failCount}`);
    return { successCount, failCount };
  }

  // 发送心跳
  startHeartbeat() {
    this.heartbeatInterval = setInterval(() => {
      this.clients.forEach((client) => {
        try {
          client.response.write(': heartbeat\n\n');
        } catch (error) {
          this.removeClient(client.id);
        }
      });
    }, 25000); // 25秒心跳
  }

  // 移除客户端
  removeClient(clientId) {
    const client = this.clients.get(clientId);
    if (client) {
      try {
        if (client.response.writable) {
          client.response.end();
        }
      } catch (error) {
        // 忽略关闭错误
      }
      this.clients.delete(clientId);
      console.log(`客户端 ${clientId} 已断开,剩余连接: ${this.clients.size}`);
    }
  }

  // 生成客户端ID
  generateClientId() {
    return `client_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }

  // 获取统计信息
  getStats() {
    return {
      totalClients: this.clients.size,
      clients: Array.from(this.clients.values()).map(client => ({
        id: client.id,
        ip: client.ip,
        connectedAt: client.connectedAt,
        lastActivity: client.lastActivity
      }))
    };
  }

  // 清理资源
  destroy() {
    if (this.heartbeatInterval) {
      clearInterval(this.heartbeatInterval);
    }
    this.clients.forEach((client) => {
      try {
        client.response.end();
      } catch (error) {
        // 忽略错误
      }
    });
    this.clients.clear();
  }
}

// 初始化连接管理器
const connectionManager = new ConnectionManager();

// SSE端点
app.get('/api/events', (req, res) => {
  connectionManager.addClient(req, res);
});
app.get('/api/stocks', (req, res) => {
  connectionManager.addClient(req, res);
});
app.get('/api/news', (req, res) => {
  connectionManager.addClient(req, res);
});

// 广播消息API
app.post('/api/broadcast', express.json(), (req, res) => {
  const { event, data, excludeClientId } = req.body;

  if (!event || !data) {
    return res.status(400).json({ error: 'Missing event or data' });
  }

  try {
    const result = connectionManager.broadcast(event, data, excludeClientId);
    res.json({
      success: true,
      ...result,
      totalClients: connectionManager.getStats().totalClients
    });
  } catch (error) {
    console.error('广播消息失败:', error);
    res.status(500).json({ error: 'Broadcast failed' });
  }
});

// 获取连接统计
app.get('/api/stats', (req, res) => {
  res.json(connectionManager.getStats());
});

// 优雅关闭
process.on('SIGTERM', () => {
  console.log('收到SIGTERM信号,优雅关闭服务器...');
  connectionManager.destroy();
  process.exit(0);
});

app.listen(3000, () => {
  console.log('SSE服务器运行在端口 3000');
});

Nginx 配置

# 针对SSE的Nginx配置
server {
    listen 80;
    server_name your-domain.com;

    location /api/events {
        proxy_pass http://backend_server;
        proxy_set_header Connection '';
        proxy_http_version 1.1;
        proxy_buffering off;
        proxy_cache off;
        proxy_read_timeout 24h;
        proxy_send_timeout 24h;

        # 重要:禁用缓冲以确保实时性
        proxy_set_header X-Accel-Buffering no;

        # 跨域支持
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Headers 'Cache-Control';
    }

    location /api/ {
        proxy_pass http://backend_server;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

浏览器兼容性和生产环境考虑

浏览器兼容性

浏览器支持版本备注
Chrome6+完全支持
Firefox6+完全支持
Safari5+完全支持
Edge79+完全支持
IE不支持需要降级方案

连接限制处理

// 浏览器连接池管理
class SSEConnectionPool {
  constructor(maxConnections = 6) { // 大多数浏览器限制为6个
    this.maxConnections = maxConnections;
    this.connections = new Map();
    this.pendingRequests = [];
    this.connectionCount = 0;
  }

  async getConnection(url, options = {}) {
    // 等待可用连接槽位
    while (this.connectionCount >= this.maxConnections) {
      await this.waitForSlot();
    }

    const connection = new AdvancedSSEClient(url, options);
    const connectionId = this.generateConnectionId();

    this.connections.set(connectionId, connection);
    this.connectionCount++;

    // 连接关闭时释放槽位
    connection.on('disconnected', () => {
      this.connections.delete(connectionId);
      this.connectionCount--;
      this.processPendingRequests();
    });

    connection.on('error', () => {
      this.connections.delete(connectionId);
      this.connectionCount--;
      this.processPendingRequests();
    });

    return connection;
  }

  waitForSlot() {
    return new Promise(resolve => {
      this.pendingRequests.push(resolve);
    });
  }

  processPendingRequests() {
    if (this.pendingRequests.length > 0 && this.connectionCount < this.maxConnections) {
      const resolve = this.pendingRequests.shift();
      resolve();
    }
  }

  generateConnectionId() {
    return `conn_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }

  // 关闭所有连接
  closeAll() {
    this.connections.forEach(connection => {
      connection.close();
    });
    this.connections.clear();
    this.connectionCount = 0;
    this.pendingRequests = [];
  }
}

移动端优化

class MobileSSEClient extends AdvancedSSEClient {
  constructor(url, options = {}) {
    super(url, {
      autoReconnect: true,
      maxRetries: Infinity, // 移动网络需要无限重试
      retryDelay: 1000,
      backoffMultiplier: 1.5,
      maxRetryDelay: 60000, // 最大重试延迟60秒
      ...options
    });

    this.setupNetworkListeners();
    this.setupVisibilityHandler();
  }

  setupNetworkListeners() {
    // 监听网络状态变化
    if (typeof navigator !== 'undefined' && navigator.connection) {
      navigator.connection.addEventListener('change', this.handleNetworkChange.bind(this));
    }

    window.addEventListener('online', () => {
      console.log('网络恢复,尝试重新连接');
      if (!this.isConnected) {
        this.connect();
      }
    });

    window.addEventListener('offline', () => {
      console.log('网络断开,关闭连接');
      this.close(); // 网络断开时主动关闭以节省资源
    });
  }

  setupVisibilityHandler() {
    // 页面可见性变化处理
    document.addEventListener('visibilitychange', () => {
      if (document.hidden) {
        // 页面隐藏时,延长心跳间隔或暂停连接
        this.onBackground();
      } else {
        // 页面可见时恢复
        this.onForeground();
      }
    });
  }

  onBackground() {
    this.backgroundMode = true;
    // 可以在这里减少心跳频率或暂停非关键连接
    console.log('应用进入后台,优化SSE连接');
  }

  onForeground() {
    this.backgroundMode = false;
    if (!this.isConnected) {
      this.connect();
    }
    console.log('应用回到前台,恢复SSE连接');
  }

  handleNetworkChange() {
    if (navigator.connection) {
      const connection = navigator.connection;
      console.log('网络连接变化:', {
        effectiveType: connection.effectiveType,
        downlink: connection.downlink,
        rtt: connection.rtt
      });

      // 根据网络质量调整重连策略
      if (connection.effectiveType === '2g' || connection.effectiveType === 'slow-2g') {
        this.options.retryDelay = 3000; // 慢网络增加重连延迟
      } else {
        this.options.retryDelay = 1000;
      }
    }
  }

  handleReconnection() {
    // 移动端使用更保守的重连策略
    const baseDelay = this.options.retryDelay;
    const backoffDelay = baseDelay * Math.pow(this.options.backoffMultiplier, this.retryCount - 1);
    const actualDelay = Math.min(backoffDelay, this.options.maxRetryDelay);

    console.log(`${actualDelay}ms后尝试第${this.retryCount}次重连...`);

    setTimeout(() => {
      // 重连前检查网络状态
      if (navigator.onLine) {
        this.connect();
      } else {
        console.log('网络未连接,等待网络恢复');
        this.handleReconnection(); // 继续等待
      }
    }, actualDelay);
  }
}

实际应用场景

实时数据仪表盘

class Dashboard {
  constructor() {
    this.connectionPool = new SSEConnectionPool();
    this.initialize();
  }

  async initialize() {
    // 使用连接池管理多个 SSE 连接
    this.eventsConnection = await this.connectionPool.getConnection(
      '/api/events',
      {
        withCredentials: true,
        autoReconnect: true,
        maxRetries: 10,
        retryDelay: 2000,
        backoffMultiplier: 1.5,
      }
    );
    this.stockConnection = await this.connectionPool.getConnection(
      '/api/stocks',
      { withCredentials: true }
    );
    this.newsConnection = await this.connectionPool.getConnection(
      '/api/news',
      { withCredentials: true }
    );

    this.setupEventHandlers();
  }

  setupEventHandlers() {
    this.eventsConnection.on('message', (event) => {
      const stockData = JSON.parse(event.data);
      console.log(`${this.eventsConnection.url}:`, stockData);
      // updateUI(stock);
    });
    this.stockConnection.on('customUpdate', (event) => {
      const stockData = JSON.parse(event.data);
      console.log(`${this.stockConnection.url}:`, stockData);
      // updateUI(stock);
    });
    this.newsConnection.on('customUpdate', (event) => {
      const stockData = JSON.parse(event.data);
      console.log(`${this.newsConnection.url}:`, stockData);
      // updateUI(stock);
    });

    // 错误处理
    this.eventsConnection.on('error', (error) => {
      console.error('数据连接异常:', error);
    });
    this.stockConnection.on('error', (error) => {
      console.error('数据连接异常:', error);
    });
    this.newsConnection.on('error', (error) => {
      console.error('数据连接异常:', error);
    });
  }

  // 清理资源
  destroy() {
    if (this.eventsConnection) {
      this.eventsConnection.destroy();
    }
    if (this.stockConnection) {
      this.stockConnection.destroy();
    }
    if (this.newsConnection) {
      this.newsConnection.destroy();
    }
    this.connectionPool.closeAll();
  }
}

import { Dashboard } from "./dashboard.js";

const startReceive = () => {
  fetch("/api/broadcast", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      event: "message",
      data: {
        symbol: "message",
      },
    }),
  });
};
const customUpdate = () => {
  fetch("/api/broadcast", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      event: "customUpdate",
      data: {
        symbol: "customUpdate",
      },
    }),
  });
};
const getStats = async () => {
  const data = await fetch("/api/stats");
  const json = await data.json();
  console.log(json);
};
const closeMessage = (stockDashboard) => {
  stockDashboard?.eventsConnection?.close()
};
const connection = async (element, stockUpdateElement, getTotalElement, closeMessageElement, closeAllElement) => {
  const stockDashboard = await new Dashboard();
  await stockDashboard?.eventsConnection?.connect();
  await stockDashboard?.stockConnection?.connect();
  await stockDashboard?.newsConnection?.connect();

  element.addEventListener("click", () => {
    startReceive();
  });
  stockUpdateElement.addEventListener("click", () => {
    customUpdate();
  });
  getTotalElement.addEventListener("click", () => {
    getStats();
  });
  closeMessageElement.addEventListener("click", () => {
    closeMessage(stockDashboard)
  });
  closeAllElement.addEventListener("click", () => {
    stockDashboard.destroy();
  });
};
export { connection };

生产环境监控和调试

// SSE监控工具
class SSEMonitor {
    constructor() {
        this.metrics = {
            connections0,
            messagesSent0,
            messagesReceived0,
            errors0,
            reconnections0
        };
        
        this.setupMonitoring();
    }

    setupMonitoring() {
        // 定期报告指标
        setInterval(() => {
            this.reportMetrics();
        }, 60000); // 每分钟报告一次

        // 监听页面卸载以报告最终指标
        window.addEventListener('beforeunload', () => {
            this.reportMetrics(true);
        });
    }

    trackConnection(client) {
        this.metrics.connections++;
        
        client.on('connected', () => {
            console.log('SSE连接建立:', client.url);
        });

        client.on('message', () => {
            this.metrics.messagesReceived++;
        });

        client.on('error', (error) => {
            this.metrics.errors++;
            console.error('SSE错误:', error);
            this.reportError(error);
        });

        client.on('disconnected', () => {
            this.metrics.connections--;
        });
    }

    reportMetrics(final = false) {
        const metrics = {
            ...this.metrics,
            timestampnewDate().toISOString(),
            userAgent: navigator.userAgent,
            final: final
        };

        // 发送到监控服务
        if (navigator.sendBeacon) {
            navigator.sendBeacon('/api/metrics/sse'JSON.stringify(metrics));
        } else {
            fetch('/api/metrics/sse', {
                method'POST',
                bodyJSON.stringify(metrics),
                keepalivetrue
            });
        }

        console.log('SSE指标报告:', metrics);
    }

    reportError(error) {
        const errorInfo = {
            type'sse_error',
            message: error.message,
            stack: error.stack,
            timestampnewDate().toISOString(),
            urlwindow.location.href
        };

        // 错误上报
        fetch('/api/errors/sse', {
            method'POST',
            bodyJSON.stringify(errorInfo),
            keepalivetrue
        });
    }
}

// 初始化监控
const sseMonitor = new SSEMonitor();

总结

Server-Sent Events (SSE) 作为一种简单而高效的服务器推送技术,在现代Web开发中扮演着重要角色。通过本文的详细讲解,我们可以看到:

SSE的核心优势:

  1. 协议简单:基于标准HTTP,易于理解和实现
  2. 自动恢复:内置重连机制和消息恢复
  3. 资源友好:相比轮询更节省资源
  4. 浏览器原生支持:现代浏览器直接支持

适用场景:

  • ✅ 实时通知和警报系统
  • ✅ 数据监控和实时仪表盘
  • ✅ 新闻推送和社交媒体更新
  • ✅ 文件处理进度跟踪
  • ✅ 实时数据流展示

不适用场景:

  • ❌ 需要双向通信的应用
  • ❌ 高频、低延迟的交易系统
  • ❌ 需要传输二进制数据的场景

生产环境要点:

  1. 连接管理:注意浏览器连接数限制
  2. 错误处理:实现健壮的重连和降级策略
  3. 资源清理:及时关闭连接避免内存泄漏
  4. 监控告警:建立完整的监控体系
  5. 安全考虑:实施适当的认证和授权

SSE提供了一种在特定场景下比WebSocket更简单的解决方案。正确理解和使用SSE,可以帮助我们构建更高效、更可靠的实时Web应用。随着Web技术的不断发展,SSE仍然在许多场景下保持着其独特的价值和优势。