SSE与Websocket、Http的关系

发布于:2025-09-11 ⋅ 阅读:(16) ⋅ 点赞:(0)

简单来说:SSE 是一种基于 HTTP 的技术标准,用于实现服务器向客户端的单向实时通信。

下面我们分点详细解释:

1. 什么是 SSE?

SSE 的全称是 Server-Sent Events(服务器发送事件)。

  • 目的:它的主要目的是允许服务器在任何时候主动向客户端(通常是浏览器)推送数据。

  • 特性:它是一种单向通信通道。数据流只能从服务器流向客户端。客户端无法通过这个连接向服务器发送数据(除了最初的连接请求)。

  • 协议:它是一个Web API,在浏览器端通过 JavaScript 的 EventSource 接口来实现。同时,它也是一种简单的、基于文本的数据格式协议。

  • 本质:SSE 本质上是对 HTTP 协议的一种创新使用,它没有创造一个新的协议,而是充分利用了 HTTP 的长连接和流式传输特性。

2. SSE 和 HTTP 的关系

SSE 与 HTTP 的关系可以概括为:SSE 构建于标准 HTTP 之上,是 HTTP 的一种特定用法。

具体体现在以下几个方面:

a. 基于 HTTP 协议
SSE 连接是通过发起一个普通的 HTTP GET 请求来建立的。客户端(浏览器)使用 EventSource API 向服务器的一个特定 URL 发送请求。这个请求看起来和任何其他网页、图片或 API 请求没有任何区别。

b. 使用标准的 HTTP 头
为了启动一个 SSE 连接,服务器在响应中必须设置一个特殊的 HTTP 头:
Content-Type: text/event-stream
这个头信息告诉客户端:“接下来的响应体不是一个一次性返回的完整文档,而是一个遵循 SSE 格式的事件流。” 一旦设置了这个头,连接就会保持打开状态。

c. 长连接(Long-Lived Connection)
与传统 HTTP 的“请求-响应-断开”模式不同,SSE 要求服务器保持这个 HTTP 连接处于打开状态。这样,服务器就可以通过这个持久的连接,连续地、多次地发送数据片段,而不是在发送一次响应后立即关闭连接。

d. 数据格式是纯文本流
通过这个保持打开的 HTTP 连接,服务器发送的数据遵循一个简单的文本格式。每条消息由几个字段组成,最常见的是 data 和 event

示例:
服务器发送的数据可能看起来像这样:

text

event: message
data: 这是一条普通消息

data: 这是一条多行的
data: 消息内容

event: update
data: {"userId": 123, "status": "online"}

客户端上的 EventSource 会解析这个流,并根据 event 字段触发相应的事件处理函数。

3. 与类似技术(如 WebSocket)的对比

为了更好地理解 SSE,通常会把它和 WebSocket 进行比较:

特性 Server-Sent Events (SSE) WebSocket
通信方向 单向(服务器 -> 客户端) 双向(全双工通信)
协议 基于 HTTP 独立的协议ws:// 或 wss://
协议开销 非常轻量,使用纯文本格式 有独立的帧结构,比 SSE 略复杂
自动重连 内置支持,客户端自动处理 需要手动实现
适用场景 实时通知、新闻推送、状态更新、仪表盘 聊天应用、在线游戏、协同编辑等需要高频双向通信的场景
浏览器支持 良好(除 IE 外) 非常好(包括现代 IE)

总结

  • 关系:SSE 不是一个独立于 HTTP 的协议,而是 HTTP 协议的一种高级应用形式。它利用一个长时间保持打开的 HTTP 连接,来实现服务器到客户端的单向数据流。

  • 核心:SSE 的核心在于 Content-Type: text/event-stream 这个 HTTP 响应头和特定的文本数据格式

  • 优势:它非常简单易用,尤其适用于那些只需要服务器向客户端推送数据的场景(如实时新闻feed、股票价格更新、服务器任务进度通知等),并且享受 HTTP 的所有好处,如身份验证、安全性(HTTPS)和兼容现有的基础设施。

因此,当你使用 SSE 时,你实际上是在以一种更高效、更实时的方式使用 HTTP。

SSE 如何实现服务器推送

SSE 只使用 HTTP GET 方法建立连接,之后的所有数据推送都是通过保持这个GET连接开放来实现的,不需要使用POST或其他HTTP方法。

详细解释

1. 连接建立阶段

  • 客户端使用 GET 请求发起SSE连接

  • 服务器响应时设置特殊头部,表明这是一个事件流

  • 服务器不关闭这个连接,而是保持它开放

2. 数据传输阶段

  • 服务器通过这个保持开放的连接多次写入数据

  • 数据格式遵循SSE规范:data: 内容\n\n

  • 整个过程都在同一个GET连接中进行

3. 连接终止

  • 服务器或客户端可以关闭连接

  • 客户端自动尝试重新连接(使用新的GET请求)

完整示例

前端代码 (HTML + JavaScript)

html

<!DOCTYPE html>
<html>
<head>
    <title>SSE HTTP 方法演示</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
        .container { border: 1px solid #ddd; padding: 20px; border-radius: 5px; margin-top: 20px; }
        .request-info { background-color: #f8f9fa; padding: 15px; border-radius: 5px; margin-bottom: 20px; }
        .connection-status { padding: 10px; border-radius: 4px; margin-bottom: 15px; }
        .connected { background-color: #d4edda; color: #155724; }
        .disconnected { background-color: #f8d7da; color: #721c24; }
        .event-log { height: 300px; overflow-y: auto; border: 1px solid #ddd; padding: 10px; }
        .event { margin-bottom: 8px; padding: 5px; border-bottom: 1px solid #eee; }
        .get-badge { background-color: #007bff; color: white; padding: 2px 6px; border-radius: 4px; font-size: 12px; }
    </style>
</head>
<body>
    <h1>SSE HTTP 方法演示</h1>
    <p>此演示展示SSE如何仅使用HTTP GET方法实现服务器向客户端推送数据。</p>
    
    <div class="request-info">
        <h3>HTTP 请求信息</h3>
        <p>方法: <span class="get-badge">GET</span></p>
        <p>端点: <code>/sse-stream</code></p>
        <p>响应头: <code>Content-Type: text/event-stream</code></p>
    </div>
    
    <div class="container">
        <div id="status" class="connection-status disconnected">
            状态: 未连接
        </div>
        
        <div>
            <button οnclick="connectSSE()">建立SSE连接</button>
            <button οnclick="disconnectSSE()">断开连接</button>
            <button οnclick="sendMessage()" id="send-btn" disabled>发送消息到服务器</button>
        </div>
        
        <h3>事件日志:</h3>
        <div id="event-log" class="event-log"></div>
    </div>

    <script>
        let eventSource = null;
        let messageCount = 0;
        
        function connectSSE() {
            addLog('系统', '发起 GET 请求到 /sse-stream');
            
            // 创建 SSE 连接 - 使用GET方法
            eventSource = new EventSource('/sse-stream');
            
            eventSource.onopen = function() {
                updateStatus('connected', '状态: 已连接 (GET请求已建立)');
                addLog('系统', 'GET连接已建立,等待服务器推送数据...');
                document.getElementById('send-btn').disabled = false;
            };
            
            eventSource.onmessage = function(event) {
                messageCount++;
                const data = JSON.parse(event.data);
                addLog('服务器推送', `序号: ${messageCount}, 时间: ${data.time}, 消息: "${data.message}"`);
            };
            
            eventSource.onerror = function() {
                updateStatus('disconnected', '状态: 连接错误');
                addLog('错误', '连接发生错误');
                document.getElementById('send-btn').disabled = true;
            };
        }
        
        function disconnectSSE() {
            if (eventSource) {
                eventSource.close();
                eventSource = null;
                updateStatus('disconnected', '状态: 已断开');
                addLog('系统', 'GET连接已关闭');
                document.getElementById('send-btn').disabled = true;
            }
        }
        
        function sendMessage() {
            // 注意:这不是通过SSE发送的,SSE是单向的
            // 这里使用单独的POST请求模拟向服务器发送消息
            fetch('/api/message', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({ message: '客户端消息' })
            })
            .then(response => response.json())
            .then(data => {
                addLog('客户端发送', '使用POST方法发送消息到服务器');
            })
            .catch(error => {
                addLog('错误', '发送消息失败: ' + error);
            });
        }
        
        function updateStatus(status, message) {
            const statusElem = document.getElementById('status');
            statusElem.textContent = message;
            statusElem.className = `connection-status ${status}`;
        }
        
        function addLog(type, message) {
            const logElem = document.getElementById('event-log');
            const time = new Date().toLocaleTimeString();
            logElem.innerHTML += `<div class="event"><b>[${time}] ${type}:</b> ${message}</div>`;
            logElem.scrollTop = logElem.scrollHeight;
        }
    </script>
</body>
</html>

后端代码 (Node.js + Express)

javascript

const express = require('express');
const app = express();
const port = 3000;

// 中间件
app.use(express.json());
app.use(express.static('public')); // 提供静态文件

// 允许跨域
app.use((req, res, next) => {
    res.header('Access-Control-Allow-Origin', '*');
    res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
    next();
});

// 存储所有活动的SSE连接
const clients = new Map();

// SSE端点 - 只使用GET方法
app.get('/sse-stream', (req, res) => {
    console.log('新的SSE连接建立 - 方法:', req.method);
    
    // 设置SSE必需的头部
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');
    res.setHeader('Access-Control-Allow-Origin', '*');
    
    // 生成客户端ID
    const clientId = Date.now();
    clients.set(clientId, res);
    
    console.log(`客户端 ${clientId} 已连接,当前连接数: ${clients.size}`);
    
    // 发送欢迎消息
    res.write(`data: ${JSON.stringify({
        time: new Date().toISOString(),
        message: "欢迎! 连接已通过GET方法建立",
        clientId: clientId
    })}\n\n`);
    
    let messageCount = 0;
    
    // 定期发送消息
    const intervalId = setInterval(() => {
        messageCount++;
        const data = {
            time: new Date().toISOString(),
            message: `这是服务器推送的消息 #${messageCount}`,
            clientId: clientId
        };
        
        // 通过保持开放的GET连接发送数据
        res.write(`data: ${JSON.stringify(data)}\n\n`);
        
        // 10秒后发送特殊消息
        if (messageCount === 5) {
            res.write(`data: ${JSON.stringify({
                time: new Date().toISOString(),
                message: "注意: 所有数据都是通过同一个GET连接推送的!",
                clientId: clientId
            })}\n\n`);
        }
    }, 2000);
    
    // 当客户端关闭连接时清理资源
    req.on('close', () => {
        console.log(`客户端 ${clientId} 断开连接`);
        clearInterval(intervalId);
        clients.delete(clientId);
        console.log(`当前连接数: ${clients.size}`);
    });
});

// 普通API端点 - 使用POST方法接收客户端消息
app.post('/api/message', (req, res) => {
    console.log('收到客户端消息 - 方法:', req.method);
    console.log('消息内容:', req.body);
    
    res.json({
        status: 'success',
        message: '消息已接收',
        receivedAt: new Date().toISOString()
    });
});

app.listen(port, () => {
    console.log(`SSE演示服务器运行在 http://localhost:${port}`);
    console.log('前端页面: http://localhost:3000/index.html');
});

关键理解点

  1. SSE只使用GET方法:连接建立后,所有数据推送都通过这个保持开放的GET连接进行

  2. 单向通信:SSE设计为服务器到客户端的单向通信

    • 如果需要客户端向服务器发送数据,需要使用单独的HTTP请求(如POST)

  3. 连接保持:服务器通过不结束响应来保持连接开放,从而能够多次写入数据

  4. 与WebSocket的区别

    • WebSocket是双向通信,使用自己的协议(ws://)

    • SSE是单向通信,基于标准HTTP

运行说明

  1. 创建项目文件夹,添加两个文件:

    • index.html (前端页面)

    • server.js (Node.js服务器)

  2. 安装Express:

    bash

    npm install express
  3. 启动服务器:

    bash

    node server.js
  4. 在浏览器中访问 http://localhost:3000/index.html

这个示例清晰地展示了SSE如何仅使用HTTP GET方法实现服务器向客户端的实时数据推送。


网站公告

今日签到

点亮在社区的每一天
去签到