深入理解 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 协议,无需额外的服务器配置。

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

相关推荐
加油乐2 小时前
Sass与Less的特性与区别
前端·css
鹏多多2 小时前
React自定义Hooks设计指南:从封装到复用
前端·javascript·react.js
JarvanMo2 小时前
在 Flutter 中正确处理文本缩放
前端
深蓝电商API2 小时前
HTML 解析入门:用 BeautifulSoup 轻松提取网页数据
前端·爬虫·python·beautifulsoup
excel3 小时前
JavaScript 运算符与 Vue 中的 1 << n 应用
前端
上单带刀不带妹3 小时前
Vue3 全局 API 转移详解
前端·javascript·vue.js·vue3·api
怕冷的火焰(~杰)3 小时前
yarn安装electron和better-sqlite3失败问题(rebuild:better-sqlite3)
前端·javascript·electron
IT_陈寒3 小时前
JavaScript性能优化:7个90%开发者不知道的V8引擎黑科技
前端·人工智能·后端
摸鱼的春哥3 小时前
“全栈模式”必然导致“质量雪崩”!和个人水平关系不大
前端·javascript·后端