一文了解 Server-Sent Events (SSE):构建高效的服务器推送应用

一文了解 Server-Sent Events (SSE):构建高效的服务器推送应用

引言

在当今的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消息由一系列键值对组成,每个字段以换行符结束,消息以双换行符结束。

基本语法:

makefile 复制代码
field: value\n
\n

完整示例:

javascript 复制代码
// 单条消息
event: message
data: 这是消息内容
id: 12345
retry: 5000

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

字段详解

1. data 字段(必需)

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

javascript 复制代码
// 单行数据
data: Hello World

// 多行数据
data: 第一行内容
data: 第二行内容
// 客户端接收: "第一行内容\n第二行内容"
2. event 字段(可选)

指定事件类型,默认为message

javascript 复制代码
// 默认事件
event: message
data: 普通消息

// 自定义事件
event: userUpdate
data: {"userId": 123, "name": "张三"}
3. id 字段(可选)

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

javascript 复制代码
id: 1689325200000
data: 重要消息内容
4. retry 字段(可选)

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

javascript 复制代码
retry: 3000
data: 设置重连时间为3秒

协议高级特性

注释消息

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

javascript 复制代码
: 这是一个注释,用于保持连接
: 心跳检测
data: 实际数据
多事件类型

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

javascript 复制代码
// 股票更新事件
event: stockUpdate
data: {"symbol": "AAPL", "price": 182.3}

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

客户端实现详解

基础 API 使用

1. 创建连接
javascript 复制代码
// 创建 EventSource 实例
const eventSource = new EventSource('/api/events');

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

// 跨域请求需要显式设置凭据
const eventSource = new EventSource('/api/events', {
    withCredentials: true  // 发送cookies等凭据
});
2. 事件监听
javascript 复制代码
// 监听默认消息事件
eventSource.onmessage = function(event) {
    console.log('收到消息:', event.data);
    console.log('最后事件ID:', event.lastEventId);
};

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

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

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

高级客户端实现

完整的 SSE 客户端类
javascript 复制代码
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('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();
    }
}

// 使用示例
const sseClient = new AdvancedSSEClient('/api/real-time-data', {
    withCredentials: true,
    autoReconnect: true,
    maxRetries: 10,
    retryDelay: 2000,
    backoffMultiplier: 1.5
});

sseClient.on('connected', () => {
    console.log('成功连接到服务器');
});

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

sseClient.on('stockUpdate', (event) => {
    const stockData = JSON.parse(event.data);
    updateUI(stockData);
});

sseClient.on('error', (error) => {
    console.error('SSE客户端错误:', error);
});

sseClient.connect();

服务器端实现

Node.js (Express) 实现

生产级实现
javascript 复制代码
const express = require('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) {
                throw new Error('Message too large');
            }
            return jsonString;
        } else if (typeof data === 'string') {
            // 防止注入攻击
            if (data.includes('\0') || data.length > 10000) {
                throw new Error('Invalid message content');
            }
            return data;
        } else {
            return String(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);
});

// 广播消息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 配置

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;
    }
}

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

浏览器兼容性

浏览器 支持版本 备注
Chrome 6+ 完全支持
Firefox 6+ 完全支持
Safari 5+ 完全支持
Edge 79+ 完全支持
IE 不支持 需要降级方案

连接限制处理

javascript 复制代码
// 浏览器连接池管理
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 = [];
    }
}

移动端优化

javascript 复制代码
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);
    }
}

实际应用场景

1. 实时数据仪表盘

javascript 复制代码
class StockDashboard {
    constructor() {
        this.sseClient = new MobileSSEClient('/api/stocks');
        this.connectionPool = new SSEConnectionPool();
        this.setupEventHandlers();
    }

    async initialize() {
        // 使用连接池管理多个SSE连接
        this.stockConnection = await this.connectionPool.getConnection(
            '/api/stocks',
            { withCredentials: true }
        );
        
        this.newsConnection = await this.connectionPool.getConnection(
            '/api/news',
            { withCredentials: true }
        );

        this.setupEventHandlers();
    }

    setupEventHandlers() {
        this.stockConnection.on('stockUpdate', (event) => {
            const stockData = JSON.parse(event.data);
            this.updateStockDisplay(stockData);
        });

        this.stockConnection.on('marketStatus', (event) => {
            const status = JSON.parse(event.data);
            this.updateMarketStatus(status);
        });

        this.newsConnection.on('breakingNews', (event) => {
            const news = JSON.parse(event.data);
            this.showNewsAlert(news);
        });

        // 错误处理
        this.stockConnection.on('error', (error) => {
            this.showErrorMessage('股票数据连接异常');
            console.error('Stock SSE error:', error);
        });
    }

    updateStockDisplay(stock) {
        const element = document.querySelector(`[data-symbol="${stock.symbol}"]`);
        if (element) {
            const changeClass = stock.change >= 0 ? 'positive' : 'negative';
            const changeIcon = stock.change >= 0 ? '↗' : '↘';
            
            element.innerHTML = `
                <div class="stock-card ${changeClass}">
                    <div class="symbol">${stock.symbol}</div>
                    <div class="price">$${stock.price.toFixed(2)}</div>
                    <div class="change">
                        <span class="icon">${changeIcon}</span>
                        <span class="value">${stock.change >= 0 ? '+' : ''}${stock.change.toFixed(2)}%</span>
                    </div>
                    <div class="volume">成交量: ${this.formatVolume(stock.volume)}</div>
                </div>
            `;
            
            // 添加价格变化动画
            element.classList.add('price-update');
            setTimeout(() => element.classList.remove('price-update'), 1000);
        }
    }

    formatVolume(volume) {
        if (volume >= 1000000) {
            return (volume / 1000000).toFixed(1) + 'M';
        } else if (volume >= 1000) {
            return (volume / 1000).toFixed(1) + 'K';
        }
        return volume.toString();
    }

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

2. 生产环境监控和调试

javascript 复制代码
// SSE监控工具
class SSEMonitor {
    constructor() {
        this.metrics = {
            connections: 0,
            messagesSent: 0,
            messagesReceived: 0,
            errors: 0,
            reconnections: 0
        };
        
        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,
            timestamp: new Date().toISOString(),
            userAgent: navigator.userAgent,
            final: final
        };

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

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

    reportError(error) {
        const errorInfo = {
            type: 'sse_error',
            message: error.message,
            stack: error.stack,
            timestamp: new Date().toISOString(),
            url: window.location.href
        };

        // 错误上报
        fetch('/api/errors/sse', {
            method: 'POST',
            body: JSON.stringify(errorInfo),
            keepalive: true
        });
    }
}

// 初始化监控
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仍然在许多场景下保持着其独特的价值和优势。

相关推荐
weixin_382395236 小时前
为小工厂量身打造:本地部署的物料管理系统带缺料计算
前端·制造
__zRainy__6 小时前
解决pnpm v10+不自动构建
前端·pnpm·工程化
猫猫不是喵喵.6 小时前
Vue3 Props 属性
前端·javascript·vue.js
醉城夜风~7 小时前
CSS元素显示模式(display)
前端·css
AI大模型-小华8 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子10 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅11 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni11 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学12 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端