SSE与Websocket、Http的关系
简单来说: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', '*');// 生成客户端IDconst 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'); });
关键理解点
SSE只使用GET方法:连接建立后,所有数据推送都通过这个保持开放的GET连接进行
单向通信:SSE设计为服务器到客户端的单向通信
如果需要客户端向服务器发送数据,需要使用单独的HTTP请求(如POST)
连接保持:服务器通过不结束响应来保持连接开放,从而能够多次写入数据
与WebSocket的区别:
WebSocket是双向通信,使用自己的协议(ws://)
SSE是单向通信,基于标准HTTP
运行说明
创建项目文件夹,添加两个文件:
index.html
(前端页面)server.js
(Node.js服务器)
安装Express:
bash
npm install express
启动服务器:
bash
node server.js
在浏览器中访问
http://localhost:3000/index.html
这个示例清晰地展示了SSE如何仅使用HTTP GET方法实现服务器向客户端的实时数据推送。