我是一个前端sse小白,请详细讲述相关知识点

75 阅读7分钟

哈喽,大家好我是小菜不拖延up,好久不见,又是一个拖到现在才完整整理的知识点。本篇文章在写的时候多问了问为什么,而不是简单的讲解基础的用法,本篇文章希望达成:

  • 能覆盖面试知识点
  • 能掌握sse基本用法
  • 对sse有一定了解

阅读参考: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.CONNECTING0连接中对象刚创建,或连接断开后正在尝试重连。
EventSource.OPEN1已连接连接已成功建立,可以接收数据。
EventSource.CLOSED2已关闭连接已被 .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',()=>{})

image.png

服务端实现

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的区别

SSEWebSocket
基于 HTTP 协议基于 TCP 协议
单工,只能服务端单向发送消息全双工,可以同时发送和接收消息
轻量级,使用简单相对复杂
内置断线重连和消息追踪的功能不在协议范围内,需手动实现
文本或使用 Base64 编码和 gzip 压缩的二进制消息类型广泛
支持自定义事件类型不支持自定义事件类型
连接数 HTTP/1.1 6 个,HTTP/2 可协商(默认 100)连接数无限制
SSE 基于 HTTP,因此需要显式地处理 CORS 问题建立连接时使用 HTTP Upgrade 请求,它默认会携带 Cookie 等凭据,不需要特殊的 withCredentials 标志

最后

欢迎各位大佬指导或者提出问题,up会完善的!