深入理解 SSE:服务器发送事件及其在前后端中的实践

前言

放假前接到一个需求,关于前端从服务器获取实时数据,之前用的是定时器,由于原因,诸多原因,产品拉会讨论新的方案,最后改成用SSE技术实现同样效果。

在此之前我对SSE等知识了解得比较少,因此在实践过程中写成一篇博客,和大家一起交流学习。
在实时 Web 应用开发中,我们经常需要从服务器获取实时数据。传统的轮询方式效率低下,而 WebSocket 虽然功能强大但实现复杂。

介绍一种轻量级的实时服务器推送技术 ------SSE(Server-Sent Events),以及如何在 Express 后端和 Vue2 前端中实现它。

一、什么是 SSE?

SSE(Server-Sent Events,服务器发送事件)是一种基于 HTTP 协议的技术,允许服务器主动向客户端持续推送数据。它的工作方式很简单:

  • 客户端发起一次 HTTP 请求建立连接
  • 服务器保持这个连接不关闭,有新数据时主动推送给客户端
  • 数据以简单的文本格式传输,每次推送都遵循特定格式
  • 当连接意外断开时,客户端会自动尝试重连

SSE 就像服务器给客户端开了一个专属推送通道,服务器可以随时向这个通道发送消息,而不需要客户端反复请求。

二、SSE 与 WebSocket 的区别

SSE 和 WebSocke都能实现服务器向客户端推送数据,但适用场景不同:

特性 SSE WebSocket
通信方式 单向(服务器→客户端) 双向(全双工)
协议 基于 HTTP 独立的 WebSocket 协议
实现复杂度 简单(浏览器原生支持) 较复杂
重连机制 自带自动重连 需要手动实现
数据格式 文本(简单格式) 二进制或文本
适用场景 通知、实时数据展示 聊天、游戏等双向交互

简单来说,如果你只需要服务器向客户端(单向通信) 推送数据(如实时监控面板、新闻推送),SSE 是更轻量的选择;如果需要双向通信(如即时聊天),则应该选择 WebSocket。

三、基础使用:前端如何使用 SSE?

现代浏览器(除 IE 外)原生支持 SSE,通过EventSource API 即可轻松使用:

javascript 复制代码
// 建立连接
const source = new EventSource('/api/sse-endpoint');

// 监听消息事件
source.onmessage = (event) => {
  console.log('收到数据:', event.data);
  // 处理数据...
};

// 监听连接打开
source.onopen = () => {
  console.log('连接已建立');
};

// 监听错误
source.onerror = (error) => {
  console.error('发生错误:', error);
};

// 关闭连接(必要时)
// source.close();

SSE 还支持自定义事件类型,让我们可以更灵活地处理不同类型的消息:

javascript 复制代码
// 监听自定义事件
source.addEventListener('temperatureUpdate', (event) => {
  console.log('温度更新:', event.data);
});

source.addEventListener('systemMessage', (event) => {
  console.log('系统消息:', event.data);
});

四、实战演练

1、Express 后端实现 SSE

ps:如何创建express应用程序在本篇文章不会提及,有需要请阅读文章:

https://blog.csdn.net/orbit4/article/details/144678842

讲解如何在 Express 中实现 SSE 服务,主要分为以下几个步骤:

1. 设置正确的响应头

SSE 需要特定的 HTTP 头来告知客户端这是一个事件流:

javascript 复制代码
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
2. 保持连接并发送数据

与普通 HTTP 请求不同,SSE 连接需要保持打开状态。我们使用res.write()而不是res.send()res.end()来发送数据:

javascript 复制代码
// 存储所有客户端连接
let clients = [];

router.get('/', (req, res) => {
  // 设置SSE响应头
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');
  res.flushHeaders();

  // 将新客户端添加到列表
  clients.push(res);
  
  // 发送连接确认
  res.write(`event: connected\n`);
  res.write(`data: ${JSON.stringify({ message: '已建立SSE连接' })}\n\n`);

  // 处理客户端断开
  req.on('close', () => {
    clients = clients.filter(client => client !== res);
  });
});
3. 遵循 SSE 数据格式

服务器发送的每条消息必须遵循特定格式:

  • data:开头,后跟实际内容
  • 每条消息以两个换行符 (\n\n) 结束
  • 可选指定event:字段来定义事件类型
  • 可选指定id:字段来标识消息
  • 可选指定retry:字段来建议重连间隔
javascript 复制代码
// 定期向所有客户端发送数据
setInterval(() => {
  const data = {
    timestamp: new Date().toISOString(),
    temperature: (20 + Math.random() * 10).toFixed(2),
    humidity: (40 + Math.random() * 30).toFixed(2)
  };
  
  // 向每个连接的客户端发送数据
  clients.forEach(client => {
    client.write(`data: ${JSON.stringify(data)}\n\n`);
  });
}, 2000);
4. 处理跨域问题

由于我们的前端和后端可能运行在不同端口,需要配置 CORS:

//安装

npm install cors

javascript 复制代码
const cors = require('cors');

// 配置CORS允许所有来源
app.use(cors({
  origin: "*",
  methods: ["GET", "POST"],
  allowedHeaders: ["Content-Type"]
}));

2、Vue2 前端实现 SSE 客户端

在 Vue2 中使用 SSE,我们可以创建一个专门的组件来管理连接和处理数据:

1. 连接管理

在组件中,我们可以通过按钮控制 SSE 连接的建立和关闭:

javascript 复制代码
methods: {
  toggleConnection() {
    if (this.isConnected) {
      this.disconnect();
    } else {
      this.connect();
    }
  },
  
  connect() {
    // 连接到后端SSE服务
    this.eventSource = new EventSource('http://localhost:3000/sse');
    
    // 监听消息事件
    this.eventSource.onmessage = (event) => {
      try {
        const data = JSON.parse(event.data);
        this.addMessage(data, false);
        this.startTypingEffect(this.messages.length - 1);
      } catch (error) {
        console.error('解析SSE数据失败:', error);
      }
    };
    
    // 监听连接打开和错误事件...
  },
  
  disconnect() {
    if (this.eventSource) {
      this.eventSource.close();
      this.eventSource = null;
      this.isConnected = false;
    }
  }
}
2. 实现打印机效果

为了让新消息以打字机效果展示,我们可以逐字显示文本并添加光标闪烁动画:

javascript 复制代码
startTypingEffect(messageIndex) {
  const message = this.messages[messageIndex];
  if (message.isSystem) return;
  
  // 为每个字段启动打字效果
  Object.keys(message.content).forEach((field, fieldIndex) => {
    const text = message.content[field];
    const elementSelector = `.message-item:nth-child(${messageIndex + 1}) .data-field:nth-child(${fieldIndex + 1}) .typed-text`;
    
    this.$nextTick(() => {
      const element = this.$el.querySelector(elementSelector);
      if (element) {
        let i = 0;
        element.textContent = '';
        
        const typingInterval = setInterval(() => {
          if (i < text.length) {
            element.textContent += text.charAt(i);
            i++;
          } else {
            clearInterval(typingInterval);
          }
        }, 50); // 控制打字速度
      }
    });
  });
}

配合 CSS 实现光标闪烁效果:

html 复制代码
.typed-text {
  border-right: 2px solid #333;
  padding-right: 3px;
  animation: blink 0.7s step-end infinite;
}

@keyframes blink {
  from, to { border-color: transparent }
  50% { border-color: #333 }
}

3、案例效果

在控制台查看长连接实时传递数据

总结

SSE 是一种简单高效的服务器推送技术,非常适合只需要服务器向客户端单向发送数据的场景。与 WebSocket 相比,它实现简单且基于 HTTP 协议,无需额外的服务器配置。

本文只是一个简单的案例,有不足之处还请大家点出。

相关推荐
恋猫de小郭7 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅13 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606114 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了14 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅14 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅15 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅15 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment15 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅15 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊15 小时前
jwt介绍
前端