HTML5 服务器发送事件(Server-Sent Events)(一文讲透)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新开坑项目:《Spring AI 项目实战》 正在持续爆肝中,基于 Spring AI + Spring Boot 3.x + JDK 21..., 点击查看 ;
  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;演示链接: http://116.62.199.48:7070 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 90w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 3100+ 小伙伴加入学习 ,欢迎点击围观

在互联网应用快速发展的今天,实时数据推送技术成为开发者关注的热点。无论是股票价格的实时更新、社交媒体的消息推送,还是物联网设备的数据监控,都需要一种高效可靠的通信方式。HTML5 服务器发送事件(Server-Sent Events)(简称 SSE)正是为了解决这类需求而诞生的技术。它以“单向数据流”的特性,为开发者提供了一种轻量级、易实现的服务器到客户端通信方案。本文将从基础概念、实现细节到实际案例,逐步解析 SSE 的工作原理和应用场景,帮助读者掌握这一实用技术。


什么是 Server-Sent Events?

Server-Sent Events(SSE) 是 HTML5 引入的一种标准化协议,允许服务器主动向客户端推送实时数据。与传统的轮询(Polling)或 WebSocket 的双向通信不同,SSE 是一种单向、长连接的通信方式,服务器通过 HTTP 协议持续向客户端发送数据,而客户端无需主动请求即可实时接收更新。

可以将其想象为“数据的单行道”:服务器像一位热情的快递员,不断将包裹(数据)送到客户端家门口,而客户端只需安静等待并签收即可。这种方式既避免了轮询的高延迟和资源浪费,又比 WebSocket 更简单易用,特别适合单向数据流的场景。


SSE 的核心特性与优势

1. 单向通信,轻量高效

SSE 的核心是单向数据流,这意味着服务器可以持续推送数据,而客户端仅需被动接收。这种设计减少了通信开销,尤其适合服务器主导的场景,例如:

  • 实时股价、天气数据更新
  • 后台任务进度通知
  • 日志实时监控

2. 基于 HTTP/1.1,兼容性强

SSE 完全依赖标准的 HTTP 协议,无需额外协议支持。服务器和客户端通过长连接保持通信,而 HTTP 的广泛兼容性使得 SSE 可以无缝集成到现有系统中。

3. 自动重连机制

当网络中断或连接关闭时,SSE 的客户端会自动尝试重新连接,开发者无需手动处理这一过程。这一特性极大简化了容错逻辑的编写。

4. 简单易实现

与 WebSocket 需要复杂的握手协议和双向通信机制相比,SSE 的客户端仅需一行代码即可建立连接,服务器端的实现也相对简单,适合快速开发。


SSE 的基本工作原理

1. 客户端发起连接

客户端通过 JavaScript 的 EventSource 对象向服务器发起请求,指定 SSE 的端点(Endpoint):

// 创建 EventSource 实例,连接到服务器的 SSE 端点
const eventSource = new EventSource('/stream');

// 监听消息事件,接收服务器推送的数据
eventSource.addEventListener('message', (event) => {
  console.log('Received:', event.data);
});

// 处理连接打开事件
eventSource.addEventListener('open', () => {
  console.log('Connected to server!');
});

// 处理错误事件
eventSource.addEventListener('error', (err) => {
  console.error('Connection failed:', err);
});

2. 服务器持续推送数据

服务器需要返回符合 SSE 格式的响应,响应头必须包含以下两个关键字段:

  • Content-Type: text/event-stream
  • Cache-Control: no-cache(可选,但推荐避免缓存问题)

数据格式则遵循简单的文本协议,每条消息以 data: 开头,后跟换行符,示例如下:

id: 123  
event: update  
data: {"temperature": 25.5, "humidity": 60}  

data: {"stock_price": 150.20}  
  • id:可选字段,用于标识消息的唯一性,帮助服务器在断线后恢复状态。
  • event:自定义事件类型,允许客户端根据事件名称处理不同逻辑。
  • data:必填字段,携带具体数据内容。

3. 自动重连与断线恢复

当连接因网络问题中断时,客户端会自动尝试重新连接,默认等待时间从 500 毫秒开始,每次递增,直到成功。若需自定义重连间隔,可通过服务器返回的 retry 字段指定:

retry: 2000  // 2秒后重试

实战案例:构建实时股票价格推送系统

场景描述

假设我们正在开发一个股票交易应用,需要实时显示某只股票的价格变动。通过 SSE,服务器可以持续推送最新价格,客户端无需频繁轮询即可实时更新界面。

1. 服务器端实现(Node.js 示例)

使用 Express 框架创建一个简单的 SSE 端点:

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

app.get('/stock-stream', (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  // 模拟每秒推送随机价格
  const interval = setInterval(() => {
    const price = Math.random() * 200 + 50; // 生成50-250的随机数
    res.write(`data: ${JSON.stringify({ price: price.toFixed(2) })}\n\n`);
  }, 1000);

  // 处理客户端关闭连接
  req.on('close', () => {
    clearInterval(interval);
    res.end();
  });
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

2. 客户端实现

在 HTML 页面中,通过 EventSource 接收数据并更新 DOM:

<!DOCTYPE html>
<html>
<head>
  <title>实时股票价格</title>
</head>
<body>
  <div id="price">加载中...</div>

  <script>
    const eventSource = new EventSource('/stock-stream');

    eventSource.addEventListener('message', (event) => {
      const priceData = JSON.parse(event.data);
      document.getElementById('price').textContent = `当前价格: $${priceData.price}`;
    });

    eventSource.addEventListener('error', (err) => {
      console.error('连接失败,请刷新页面重试');
    });
  </script>
</body>
</html>

3. 运行效果

访问页面后,客户端会立即建立与服务器的连接,服务器每秒推送一次随机生成的股票价格,页面上的价格数值会实时更新。若手动关闭服务器,客户端会自动重连并继续接收数据。


进阶技巧:事件类型与自定义消息

SSE 允许通过 event 字段定义多种消息类型,从而实现更灵活的逻辑处理。例如,服务器可以同时推送股票价格、交易量、新闻事件等不同数据:

// 服务器端发送不同类型的事件
res.write(`event: price_update\ndata: {"price": 150.20}\n\n`);
res.write(`event: news_alert\ndata: "Breaking: 公司发布财报"\n\n`);

客户端则可以通过监听不同事件名来执行对应操作:

eventSource.addEventListener('price_update', (event) => {
  updatePriceDisplay(JSON.parse(event.data));
});

eventSource.addEventListener('news_alert', (event) => {
  showNotification(event.data);
});

SSE 与 WebSocket 的对比

1. 单向 vs 双向

  • SSE:仅支持服务器到客户端的单向通信,适合广播类场景(如直播弹幕、系统通知)。
  • WebSocket:支持双向通信,适合需要客户端主动发送数据的场景(如在线游戏、聊天室)。

2. 协议复杂度

SSE 基于 HTTP,实现简单;而 WebSocket 需要建立专用连接,且需处理握手、心跳包等细节。

3. 适用场景建议

需求类型推荐技术
实时推送系统状态SSE
双向聊天或协作WebSocket
移动端低耗电场景SSE(长连接更省电)

常见问题与解决方案

Q1: 如何避免服务器推送过载?

可通过以下方式控制推送频率:

  • 服务器端设置间隔时间(如示例中的 setInterval
  • 客户端在收到消息后,通过 eventSource.close() 手动暂停连接

Q2: 如何实现跨域数据推送?

在服务器响应头中添加跨域配置:

res.setHeader('Access-Control-Allow-Origin', '*'); // 允许所有域

Q3: 如何确保消息顺序不乱?

使用 id 字段为每条消息分配唯一标识,当客户端重连时,可通过 Last-Event-ID 请求头告知服务器当前最后收到的 ID,以便服务器从断点续传。


结论

HTML5 服务器发送事件(Server-Sent Events) 是一种高效、轻量的实时通信技术,特别适合单向数据流场景。通过本文的讲解,读者可以掌握 SSE 的基础原理、实现方法及实际应用案例。无论是构建股票行情监控、实时聊天室,还是物联网数据面板,SSE 都能提供简洁高效的解决方案。

在开发过程中,建议结合以下最佳实践:

  • 使用 idretry 字段增强可靠性;
  • 根据场景选择 SSE 或 WebSocket;
  • 通过事件类型分类管理不同数据流。

随着实时应用需求的不断增长,掌握 SSE 将成为开发者工具箱中不可或缺的一环。希望本文能为你的技术探索提供清晰的指引!

最新发布