SSE

本文对比了SSE(Server-sent Events)与WebSocket的技术特点,介绍了SSE的工作原理及其实现方式,并探讨了两种技术在不同场景下的适用性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考网址:https://www.v2ex.com/t/379577


SSE ( Server-sent Events )是 WebSocket 的一种轻量代替方案,使用 HTTP 协议。

严格地说,HTTP 协议是没有办法做服务器推送的,但是当服务器向客户端声明接下来要发送流信息时,客户端就会保持连接打开,SSE 使用的就是这种原理。

SSE 能做什么

理论上, SSE 和 WebSocket 做的是同一件事情。当你需要用新数据局部更新网络应用时,SSE 可以做到不需要用户执行任何操作,便可以完成。

举例我们要做一个统计系统的管理后台,我们想知道统计数据的实时情况。类似这种更新频繁、 低延迟的场景,SSE 可以完全满足。

其他一些应用场景:例如邮箱服务的新邮件提醒,微博的新消息推送、管理后台的一些操作 实时同步等,SSE 都是不错的选择。

SSE vs. WebSocket

SSE 是单向通道,只能服务器向客户端发送消息,如果客户端需要向服务器发送消息,则需要一个新的 HTTP 请求。 这对比 WebSocket 的双工通道来说,会有更大的开销。这么一来的话就会存在一个「什么时候才需要关心这个差异?」的问题,如果平均每秒会向服务器发送一次消息的话,那应该选择 WebSocket。如果一分钟仅 5 - 6 次的话,其实这个差异并不大。

在浏览器兼容方面,两者差不多。在较早之前,每当需要建立双向 Socket 时就会使用 Flash,在 移动浏览器不支持 Flash 的情况下,WebSocket 的兼容是比较难做的。

SSE 我认为最大的优势是便利:

  • 实现一个完整的服务仅需要少量的代码;
  • 可以在现有的服务中使用,不需要启动一个新的服务;
  • 可以用任何一种服务端语言中使用;
  • 基于 HTTP / HTTPS 协议,可以直接运行于现有的代理服务器和认证技术。

有了这些优势,在选择使用 SSE 时就已经为自己的项目节约了不少成本。

简单示例

下面是一个简单的示例,实现一个 SSE 服务。

服务器

'use strict';

const http = require('http');

http.createServer((req, res) => {

  // 服务器声明接下来发送的是事件流
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
    'Access-Control-Allow-Origin': '*',
  });

  // 发送消息
  setInterval(() => {
    res.write('event: slide\n'); // 事件类型
    res.write(`id: ${+new Date()}\n`); // 消息 ID
    res.write('data: 7\n'); // 消息数据
    res.write('retry: 10000\n'); // 重连时间
    res.write('\n\n'); // 消息结束
  }, 3000);

  // 发送注释保持长连接
  setInterval(() => {
    res.write(': \n\n');
  }, 12000);
}).listen(2000);

服务器首先向客户端声明接下来发送的是事件流( text/event-stream )类型的数据,然后就可以向客户端多次发送消息。

事件流是一个简单的文本流,仅支持 UTF-8 格式的编码。每条消息以一个空行作为分隔符。

在规范中为消息定义了 4 个字段:

event 消息的事件类型。客户端收到消息时,会在当前的 EventSource 对象上触发一个事件,这个事件的名称就是这个字段的值,如果消息没有这个字段,客户端的 EventSource 对象就会触发默认的 message 事件。

id 这条消息的 ID。客户端接收到消息后,会把这个 ID 作为内部属性 Last-Event-ID,在断开重连 成功后,会把 Last-Event-ID 发送给服务器。

data 消息的数据字段。 客户端会把这个字段解析为字符串,如果一条消息有多个 data 字段,客户端会自动用换行符 连接成一个字符串。

retry 指定客户端重连的时间。只接受整数,单位是毫秒。如果这个值不是整数则会被自动忽略。

一个很有意思的地方是,规范中规定以冒号开头的消息都会被当作注释,一条普通的注释(:\n\n)对于服务器来说只占 5 个字符,但是发送到客户端上的时候不会触发任何事件,这对客户端来说是非常友好的。所以注释一般被用于维持服务器和客户端的长连接。

客户端

我们创建了一个 EventSource 对象,传入参数:url。并且根据服务器的状态和发送的信息作出响应。

'use strict';

if (window.EventSource) {

  // 创建 EventSource 对象连接服务器
  const source = new EventSource('http://localhost:2000');

  // 连接成功后会触发 open 事件
  source.addEventListener('open', () => {
    console.log('Connected');
  }, false);

  // 服务器发送信息到客户端时,如果没有 event 字段,默认会触发 message 事件
  source.addEventListener('message', e => {
    console.log(`data: ${e.data}`);
  }, false);

  // 自定义 EventHandler,在收到 event 字段为 slide 的消息时触发
  source.addEventListener('slide', e => {
    console.log(`data: ${e.data}`); // => data: 7
  }, false);

  // 连接异常时会触发 error 事件并自动重连
  source.addEventListener('error', e => {
    if (e.target.readyState === EventSource.CLOSED) {
      console.log('Disconnected');
    } else if (e.target.readyState === EventSource.CONNECTING) {
      console.log('Connecting...');
    }
  }, false);
} else {
  console.error('Your browser doesn\'t support SSE');
}

EventSource从父接口 EventTarget 中继承了属性和方法,其内置了 3 个 EventHandler 属性、2 个只读属性和 1 个方法:

EventHandler 属性

EventSource.onopen 在连接打开时被调用。

EventSource.onmessage 在收到一个没有 event 属性的消息时被调用。

EventSource.onerror 在连接异常时被调用。

只读属性

EventSource.readyState 一个 unsigned short 值,代表连接状态。可能值是 CONNECTING (0), OPEN (1), 或者 CLOSED (2)。

EventSource.url 连接的 URL。

方法

EventSource.close() 关闭连接。

SSE 如何保证数据完整性

客户端在每次接收到消息时,会把消息的 id 字段作为内部属性 Last-Event-ID 储存起来。

SSE 默认支持断线重连机制,在连接断开时会 触发 EventSource 的 error 事件,同时自动重连。再次连接成功时 EventSource 会把 Last-Event-ID 属性作为请求头发送给服务器,这样服务器就可以根据这个 Last-Event-ID 作出相应的处理。

这里需要注意的是,id 字段不是必须的,服务器有可能不会在消息中带上 id 字段,这样子客户端就不会存在 Last-Event-ID 这个属性。所以为了保证数据可靠,我们需要在每条消息上带上 id 字段。

减少开销

在 SSE 的草案中提到,"text/event-stream" 的 MIME 类型传输应当在静置 15 秒后自动断开。在实际的项目中也会有这个机制,但是断开的时间没有被列入标准中。

为了减少服务器的开销,我们也可以有目的的断开和重连。

简单的办法是服务器发送一个 关闭消息并指定一个重连的时间戳,客户端在触发关闭事件时关闭当前连接并创建 一个计时器,在重连时把计时器销毁 。

'use strict';

function connectSSE() {
  if (window.EventSource) {
    const source = new EventSource('http://localhost:2000');
    let reconnectTimeout;

    source.addEventListener('open', () => {
      console.log('Connected');
      clearTimeout(reconnectTimeout);
    }, false);

    source.addEventListener('pause', e => {
      source.close();
      const reconnectTime = +e.data;
      const currentTime = +new Date();
      reconnectTimeout = setTimeout(() => {
        connectSSE();
      }, reconnectTime - currentTime);
    }, false);
  } else {
    console.error('Your browser doesn\'t support SSE');
  }
}

connectSSE();

浏览器兼容

EventSource 浏览器兼容
Broswer support of EventSource from Can I Use...

向下兼容

早些时候,为了实现数据实时更新最常见的方法就是轮询。

轮询是以一个固定频率向服务器发送请求,服务器在有 数据更新时 返回新的数据,以此来管理数据的更新。这种轮询的方式不但开销大,而且更新的效率和频率有关,也不能达到及时更新的目的。

接着便出现了长轮询的方式:客户端向服务器发送请求之后,服务器会暂时把请求挂起,等到有数据更新时再返回最新的数据给客户端,客户端在接收到新的消息后再向服务器发送请求。与常规轮询的不同之处是:数据可以做到实时更新,可以减少不必要的开销。

这里有一个「选择长轮询还是常规轮询?」的命题,长轮询是不是总比常规轮询占有优势?我们可以从带宽占用的角度分析,如果一个程序数据更新太过频繁,假设每秒 2 次更新,如果使用长轮询的话每分钟要发送 120 次 HTTP 请求。如果使用常规轮询,每 5 秒发送一次请求的话, 一分钟才 20 次,从这里看,常规轮询更占有优势。

长轮询和 SSE 最关键的区别在于,每一次数据更新都需要一次 HTTP 请求。和 WebSocket 还有 SSE 一样,长轮询也会 占用一个 socket。在数据更新效率上和 SSE 差不多,一有数据更新就能检测到。加上所有浏览器都支持,是一个不错的 SSE 替代方案。


05-10
<think>好的,我现在需要处理用户关于SSE(Server-Sent Events)的查询。首先,用户提供的引用内容中有三个关于SSE的参考资料,我需要仔细分析这些内容以确保回答的准确性。用户可能希望了解SSE的基本概念、工作原理、实现方式以及应用场景。 首先,根据引用[1]和[2],SSE是一种基于HTTP的协议,允许服务器向客户端单向推送数据,适用于需要实时更新但不需要客户端频繁发送请求的场景。引用[3]提供了一个完整的代码示例,展示了前端如何使用EventSource对象连接SSE流,并处理不同的事件和错误。这些信息是构建回答的基础。 接下来,我需要确保回答结构清晰,分点说明SSE的核心特性、工作原理、使用场景以及优缺点。同时,根据用户之前的示例,可能需要生成相关问题,比如性能优化、安全性或与其他技术的对比。 需要检查是否有遗漏的关键点,例如SSE与WebSocket的区别,这在用户提供的引用中没有提到,但通常是常见问题,应该包含在回答中。此外,引用中的代码示例可以帮助解释前端实现,但可能需要补充服务器端的实现步骤,如设置HTTP响应头为text/event-stream,保持连接打开等。 还要注意用户提到的系统级指令,比如数学公式的格式,但在这个主题中可能不涉及数学公式,所以可以忽略。确保使用中文回答,并且语言自然流畅,避免技术术语过多,同时保持专业性。 最后,生成的相关问题需要与SSE紧密相关,例如协议细节、断线连机制、安全性或实际应用案例。需要确保问题覆盖用户可能感兴趣的各个方面,帮助他们进一步探索该技术。</think>### Server-Sent Events(SSE)详解 Server-Sent Events(SSE)是一种基于 HTTP 的轻量级协议,用于实现服务器向客户端**单向实时数据推送**。它通过 HTTP 长连接持续传输事件流(`text/event-stream`),适用于需要实时更新但客户端无需频繁发送请求的场景,例如新闻推送、股票行情、实时日志监控等[^1][^2]。 --- #### 一、核心特性 1. **单向通信**:仅支持服务器到客户端的单向数据推送。 2. **基于 HTTP**:无需额外协议,兼容现有基础设施。 3. **自动连**:客户端内置断线连机制。 4. **简单协议格式**:事件流以明文格式传输,包含 `data`、`event`、`id` 等字段[^1]。 --- #### 二、工作原理 1. **客户端建立连接** 使用 `EventSource` 对象连接服务器端点: ```javascript const eventSource = new EventSource('/sse-stream'); ``` 2. **服务器响应配置** 服务器需设置响应头为 `Content-Type: text/event-stream`,并保持连接开放: ```http HTTP/1.1 200 OK Content-Type: text/event-stream Connection: keep-alive ``` 3. **事件流格式** 服务器发送的数据遵循特定格式,例如: ```plaintext event: custom data: {"count": 42} data: 这是一条默认事件消息 ``` 4. **客户端事件监听** 前端通过回调处理不同事件类型: ```javascript // 默认事件 eventSource.onmessage = (e) => { console.log(e.data); }; // 自定义事件 eventSource.addEventListener('custom', (e) => { const data = JSON.parse(e.data); }); // 错误处理 eventSource.onerror = (error) => { /* 连逻辑 */ }; ``` --- #### 三、使用场景 1. **实时通知**:用户消息提醒、系统告警。 2. **数据监控**:服务器状态、IoT 设备数据流。 3. **动态内容更新**:新闻头条、社交媒体动态。 --- #### 四、与 WebSocket 的对比 | 特性 | SSE | WebSocket | |--------------------|------------------------------|-------------------------| | 通信方向 | 单向(服务器→客户端) | 双向 | | 协议复杂度 | 基于 HTTP,无需额外握手 | 独立协议,需升级连接 | | 断线连 | 自动支持 | 需手动实现 | | 数据格式 | 文本(`text/event-stream`) | 二进制或文本 | --- #### 五、局限性 1. **单向通信限制**:无法实现客户端到服务器的实时交互。 2. **并发连接数限制**:浏览器对同一域名的 SSE 连接数有限制(通常为 6 个)。 3. **老浏览器兼容性**:IE/Edge 旧版本不支持 `EventSource`[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值