哈喽,大家好我是小菜不拖延up,好久不见,又是一个拖到现在才完整整理的知识点。本篇文章在写的时候多问了问为什么,而不是简单的讲解基础的用法,本篇文章希望达成:
- 能覆盖面试知识点
- 能掌握sse基本用法
- 对sse有一定了解
sse
本文章可参考的代码: sse-serve
const http = require('http');
const { URL } = require('url');
// 创建 HTTP 服务器
const server = http.createServer((req, res) => {
const { pathname } = new URL(req.url, `http://${req.headers.host}`);
// 设置 CORS 头部,允许所有来源访问(生产环境应限制)
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET');
if (pathname === '/sse') {
// 设置 SSE 必需的响应头
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
// 'Connection': 'keep-alive',
});
console.log('客户端已连接');
// 立即发送一条欢迎消息(JSON格式)
const welcomeMessage = {
type: 'welcome',
message: '连接已建立!',
timestamp: new Date().toISOString(),
status: 'connected'
};
res.write(`data: ${JSON.stringify(welcomeMessage)}\n\n`);
// 模拟定期发送数据(每2秒发送一次时间)
const intervalId = setInterval(() => {
const time = new Date().toLocaleTimeString();
// 注意:每条消息必须以 "data: " 开头,以 "\n\n" 结尾
res.write(`data: 服务器时间: ${time}\n\n`);
console.log(`已发送: ${time}`);
}, 2000);
// 监听连接关闭事件
req.on('close', () => {
console.log('客户端断开连接');
clearInterval(intervalId); // 清除定时器
res.end(); // 结束响应
});
} else {
// 其他路径返回 404
res.writeHead(404);
res.end('Not Found');
}
});
// 启动服务器
const PORT = 3001;
server.listen(PORT, () => {
console.log(`SSE 服务器运行在 http://localhost:${PORT}`);
});
sse-client:
<!DOCTYPE html>
<html>
<head>
<title>SSE 基础示例</title>
</head>
<body>
<h1>Server-Sent Events 示例</h1>
<button onclick="connectSSE()">连接</button>
<button onclick="disconnectSSE()">断开</button>
<div id="messages" style="margin-top: 20px; border: 1px solid #ccc; padding: 10px; height: 300px; overflow-y: scroll;"></div>
<script>
let eventSource;
function connectSSE() {
// 创建 EventSource 实例连接到服务器
eventSource = new EventSource('http://localhost:3001/sse');
// 监听消息事件
eventSource.onmessage = function(event) {
try {
// 解析 JSON 数据
const jsonData = JSON.parse(event.data);
addMessage(JSON.stringify(jsonData, null, 2));
// 根据消息类型处理
switch (jsonData.type) {
case 'welcome':
console.log('收到欢迎消息:', jsonData.message);
break;
case 'time':
console.log('收到时间更新:', jsonData.time);
break;
}
} catch (error) {
// 如果不是有效的 JSON,按普通文本处理
addMessage(event.data);
}
};
// 监听连接打开事件
eventSource.onopen = function() {
addMessage('=== 连接已建立 ===');
};
// 监听错误事件
eventSource.onerror = function() {
addMessage('=== 连接错误或已关闭 ===');
};
}
function disconnectSSE() {
if (eventSource) {
eventSource.close();
addMessage('=== 连接已手动关闭 ===');
}
}
function addMessage(message) {
const messagesDiv = document.getElementById('messages');
const messageElement = document.createElement('div');
messageElement.textContent = new Date().toLocaleTimeString() + ': ' + message;
messagesDiv.appendChild(messageElement);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
</script>
</body>
</html>
- 基于http协议
- 使用EventSource创建监听,建立连接
- SSE 默认支持断线重连,WebSocket 需要自己实现。
- SSE 一般只用来传送文本,二进制数据需要编码后传送,WebSocket 默认支持传送二进制数据。
- SSE 支持自定义发送的消息类型。
eventSrouce
EventSource 是一个由浏览器提供的、用于接收 Server-Sent Events (SSE) 的客户端 JavaScript 接口,他就是专门为为sse而生的,如果没有它,就要用更底层的api来实现,非常复杂
EventSource被设计成一种 "即创即用"(Fire-and-Forget)的自动化连接- 断线即重连,它会自动在后台尝试重新连接,无需你手动干预。
// 创建 EventSource 实例连接到服务器
eventSource = new EventSource('http://localhost:3001/sse');
// // 另一种写法
// source.addEventListener('open', function (event) {
// // ...
// }, false);
// 监听消息事件
eventSource.onmessage = function(event) {
addMessage(event.data);
};
// 监听连接打开事件
eventSource.onopen = function() {
addMessage('=== 连接已建立 ===');
};
// 监听错误事件
eventSource.onerror = function() {
addMessage('=== 连接错误或已关闭 ===');
};
eventSource.close();
虽然没有显示的open方法,但是可以通过readySate监听状态
| 常量 | 值 | 状态 | 描述 |
|---|---|---|---|
EventSource.CONNECTING | 0 | 连接中 | 对象刚创建,或连接断开后正在尝试重连。 |
EventSource.OPEN | 1 | 已连接 | 连接已成功建立,可以接收数据。 |
EventSource.CLOSED | 2 | 已关闭 | 连接已被 .close() 方法永久关闭,不会自动重连。 |
EventSource的属性
| 属性 | 描述 |
|---|---|
url | 一个只读字符串,远端服务器的 URL 源 |
readSate | 只读,表明当前的连接状态 |
withCredentials | 一个布尔值,默认为 false。跨域请求时是否发送 Cookies 等凭证信息 |
onopen | 当连接成功建立时触发。 |
onmessage | 收到没有指定 event 字段的消息时触发。(下面有解释什么意思) |
onerror | 当连接发生错误时触发(例如无法建立连接、连接中断等)。发生错误后,浏览器会自动尝试重连。 |
close() | 关闭连接。一旦调用,readyState 变为 EventSource.CLOSED,并且不会自动重连。 |
//withCredentials:为了让浏览器在连接时自动发送相关的身份验证 Cookie
const eventSource = new EventSource('https://api.example.com/sse-stream', {
withCredentials: true
});
在浏览器的network当中查看效果:type字段对应的是定义的event的值,我们要监听的就是这个值,假如没有写event,这里的值就是message,假如定义了event:update,那么前端监听时
addEventListener('update',()=>{})
服务端实现
SSE服务端没有特殊的API,它就是对一个普通GET请求进行“特殊处理”的HTTP响应。
服务端在处理这个GET请求时,没有结束它(没有调用res.end()),而是把这个HTTP连接一直保持打开状态,把它变成了一个持续的、单向的数据通道。
为什么采用GET?
- 符合HTTP语义:GET用于获取数据,SSE正是从服务器获取事件流。
- 简单性原则:SSE追求极致的简单,GET是最简单、最轻量的HTTP方法。
- 缓存友好性:GET请求可以被缓存,这对某些场景有利。
数据格式:必须是UTF-8编码,具有头部信息
//它告诉浏览器:"接下来发送的数据是SSE事件流"。
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
缺失Cache-Control: no-cache后果:
- 浏览器或代理可能会缓存SSE流的一部分数据。客户端可能收到陈旧、重复或混乱的消息。
- 在连接中断重连时,浏览器可能会错误地从缓存中提供旧的响应,而不是从服务器获取新的流,导致客户端状态过时。
- SSE连接应该是实时、唯一的。缓存会完全破坏这一设计。
缺失Connection: keep-alive:
- 对于http1.1来说,本身默认就是keepalive,所以没问题
- 对于2来说本身就是多路复用和长连接的特性,浏览器会忽略这个设置,使用2自己的连接管理机制
数据由若干个message组成,每个message之间用\n\n分隔。每个message内部由若干行组成,每一行都是如下格式
[field]: value\n
id: msg1\n
data: message\n\n
event: userconnect
retry: 10000\n
| 字段 | 作用 |
|---|---|
| data | 数据 |
| event | 浏览器可以用addEventListener()监听该事件。 |
| id | 每一条数据的编号 |
| retry | 浏览器重新发起连接的时间间隔 |
没有指定event,直接onmessage可以查到,当指定时,必须使用特定监听才可以
// 为 "notification" 这个事件类型创建一个专用信箱
eventSource.addEventListener('notification', function(event) {
// 这里会接收到上面的消息
const data = JSON.parse(event.data);
console.log('通知内容:', data.text); // 输出: "You have a new notification!"
});
// onmessage 处理函数不会收到这个消息!
eventSource.onmessage = function(event) {
console.log('这条消息不会触发');
};
sse工作流程
| 步骤 | 客户端 (浏览器) | 服务器 (Node.js) | 说明 |
|---|---|---|---|
| 1. 连接 | new EventSource('/sse') | app.get('/sse', (req, res) => { ... }) | 客户端发起GET请求,服务器路由处理器被调用。 |
| 2. 准备 | - | res.writeHead(200, { 'Content-Type': 'text/event-stream', ... }) | 服务器设置正确的响应头,但不结束响应。 |
| 3. 通信 | eventSource.onmessage = (e) => { console.log(e.data); } | res.write("data: Hello\n\n"); res.write("data: World\n\n"); | 服务器随时向连接流中写入数据。客户端一收到数据(以\n\n标记一条消息结束)就触发事件。 |
| 4. 维持 | - | 连接对象 res 一直存在 | 这才是实现“长连接”的关键!服务器的 res 对象由于没有被 end(),会一直保持在内存中,成为一个持续的通道。 |
| 5. 断开 | eventSource.close(); | req.on('close', () => { clearInterval(...) }); | 客户端关闭连接时,服务器会收到 close 事件,从而执行清理逻辑(如清除定时器)。 |
sse与websocket的区别
| SSE | WebSocket |
|---|---|
| 基于 HTTP 协议 | 基于 TCP 协议 |
| 单工,只能服务端单向发送消息 | 全双工,可以同时发送和接收消息 |
| 轻量级,使用简单 | 相对复杂 |
| 内置断线重连和消息追踪的功能 | 不在协议范围内,需手动实现 |
| 文本或使用 Base64 编码和 gzip 压缩的二进制消息 | 类型广泛 |
| 支持自定义事件类型 | 不支持自定义事件类型 |
| 连接数 HTTP/1.1 6 个,HTTP/2 可协商(默认 100) | 连接数无限制 |
| SSE 基于 HTTP,因此需要显式地处理 CORS 问题 | 建立连接时使用 HTTP Upgrade 请求,它默认会携带 Cookie 等凭据,不需要特殊的 withCredentials 标志 |
最后
欢迎各位大佬指导或者提出问题,up会完善的!