SSE 是什么?从原理到实战(Java+Vue+Node全示例)

做后端开发的同学,大概率听过「SSE」这个词------尤其是在做实时推送、流式输出场景时,经常会和WebSocket、轮询放在一起讨论。但很多人对它的理解只停留在"服务端推数据",不清楚它的原理、适用场景,也不知道怎么落地实现。

今天就彻底搞懂SSE:从核心定义、工作原理,到Java(Spring Boot)、Node.js(Express)服务端实现,再到Vue客户端调用,最后对比WebSocket,帮你快速上手,再也不用被问起时支支吾吾。

一、先搞懂:SSE到底是什么?

SSE 全称 Server-Sent Events(服务器发送事件) ,是一种基于HTTP长连接的单向实时推送技术,核心作用是让服务端能够主动、持续地向客户端发送数据,而客户端只需要被动接收即可。

很多人会把它和WebSocket搞混,但记住一个关键:SSE是"单向通信",而WebSocket是"双向通信"------这也是两者最核心的区别,后续会详细对比。

核心特性(必记)

  • 单向通信:只能服务端→客户端推送数据;客户端若要向服务端发数据,需单独走普通HTTP请求(如Axios、AJAX)。
  • 基于HTTP协议 :无需新增协议、无需额外端口,用标准GET请求发起,响应头指定Content-Type: text/event-stream即可,防火墙、代理服务器友好,部署成本低。
  • 持久长连接:客户端发起一次请求后,服务端会保持连接不关闭,后续有新数据直接通过这个连接推送,避免频繁建立/断开连接的开销。
  • 浏览器原生支持 :客户端可直接用EventSource API调用,自带断线自动重连(默认重连间隔3秒),无需手动写心跳检测、重连逻辑。
  • 轻量简单:协议简单、开发成本低,无需像WebSocket那样处理握手、帧解析等复杂逻辑,适合纯单向推送场景。

工作原理(一句话讲透)

  1. 客户端通过GET请求,向服务端发起SSE连接,请求头携带相关配置(如重连间隔);

  2. 服务端接收请求后,返回响应头Content-Type: text/event-stream,并保持连接不关闭;

  3. 当服务端有新数据时,按SSE标准格式(data: 数据内容\n\n),将数据写入连接流;

  4. 客户端通过EventSource监听数据,收到后触发onmessage事件,解析并展示数据;

  5. 若连接断开(如网络异常),客户端会自动重连,无需开发者干预。

二、实战落地:3种场景全示例(Java+Node+Vue)

光懂理论不够,直接上可运行代码------覆盖Java(Spring Boot)、Node.js(Express)服务端,以及Vue客户端,复制就能跑通。

场景说明

做一个「实时时间推送」demo:服务端每秒生成当前时间,推送给客户端,客户端实时展示------模拟AI流式输出、实时通知等真实场景。

1. 服务端1:Java(Spring Boot)实现

Spring Boot 本身支持SSE,无需额外引入第三方依赖(核心是通过ResponseEntity输出text/event-stream流)。

java 复制代码
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@RestController
public class SseController {

    // 定时任务,每秒推送一次时间
    private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();

    @GetMapping("/sse/java/stream")
    public void sseStream(HttpServletResponse response) throws IOException {
        // 1. 设置响应头,指定SSE格式
        response.setContentType(MediaType.TEXT_EVENT_STREAM_VALUE);
        response.setCharacterEncoding("UTF-8");
        response.setHeader("Cache-Control", "no-cache");
        response.setHeader("Connection", "keep-alive");

        PrintWriter writer = response.getWriter();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        // 2. 每秒推送一次当前时间,持续推送(实际场景可替换为业务数据)
        executor.scheduleAtFixedRate(() -> {
            try {
                // SSE标准格式:data: 数据\n\n(必须换行两次,否则客户端无法解析)
                String time = sdf.format(new Date());
                writer.write("data: " + time + "\n\n");
                // 强制刷新,避免数据缓存
                writer.flush();

                // 模拟异常断开(可选,测试重连)
                // if (System.currentTimeMillis() % 10000 == 0) {
                //     throw new IOException("连接异常断开");
                // }
            } catch (Exception e) {
                // 关闭连接,客户端会自动重连
                executor.shutdown();
                writer.close();
            }
        }, 0, 1, TimeUnit.SECONDS);
    }
}
    

注意点:

  • 必须设置Content-Type: text/event-stream,否则客户端无法识别为SSE流;
  • 数据格式必须是data: 内容\n\n(两个换行符不可少),客户端才能正常解析;
  • PrintWriter.write()写入数据后,需调用flush()强制刷新,避免数据缓存导致推送延迟;
  • 异常时关闭连接和线程池,客户端会自动重连。

2. 服务端2:Node.js(Express)实现

之前给过简单示例,这里完善异常处理和标准格式,确保可直接运行。

先安装依赖(仅需express):

css 复制代码
npm install express --save
    

核心代码:

javascript 复制代码
const express = require('express');
const app = express();
const port = 3001;

// SSE接口:每秒推送当前时间
app.get('/sse/node/stream', (req, res) => {
    // 设置响应头
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');
    res.setHeader('Access-Control-Allow-Origin', '*'); // 跨域允许(开发环境)

    // 每秒推送一次时间
    const interval = setInterval(() => {
        try {
            const time = new Date().toLocaleString();
            // SSE标准格式:data: 数据\n\n
            res.write(`data: ${time}\n\n`);
        } catch (err) {
            // 异常时清除定时任务,关闭连接
            clearInterval(interval);
            res.end();
        }
    }, 1000);

    // 客户端断开连接时,清除定时任务
    req.on('close', () => {
        clearInterval(interval);
        res.end();
        console.log('客户端断开SSE连接');
    });
});

app.listen(port, () => {
    console.log(`Node.js SSE服务启动,端口:${port}`);
});
    

3. 客户端:Vue3项目调用示例(最常用)

Vue项目中,可直接使用浏览器原生EventSource,也可封装成工具类,方便全局调用。这里提供两种方式,按需选择。

方式1:直接在组件中使用(简单场景)

xml 复制代码
<template>
  <div class="sse-demo">
    <h3>SSE实时时间推送(Vue3)</h3>
    <p>当前服务端时间:{{ serverTime }}</p>
  </div>
</template>

<script setup>
import { ref, onUnmounted } from 'vue';

const serverTime = ref('');
let eventSource = null;

// 初始化SSE连接
const initSSE = () => {
  // 注意:替换为自己的服务端接口地址(Java或Node)
  const url = 'http://localhost:8080/sse/java/stream'; // Java服务端
  // const url = 'http://localhost:3001/sse/node/stream'; // Node服务端

  // 实例化EventSource
  eventSource = new EventSource(url);

  // 监听连接成功
  eventSource.onopen = () => {
    console.log('SSE连接成功');
  };

  // 监听服务端推送的数据(核心)
  eventSource.onmessage = (e) => {
    // e.data 就是服务端推送的内容(字符串格式)
    serverTime.value = e.data;
  };

  // 监听连接错误
  eventSource.onerror = (err) => {
    console.error('SSE连接异常:', err);
    // 异常时可手动重连(可选,原生已自带重连)
    eventSource.close();
    setTimeout(initSSE, 3000);
  };
};

// 组件挂载时初始化SSE
initSSE();

// 组件卸载时关闭SSE连接,避免内存泄漏
onUnmounted(() => {
  if (eventSource) {
    eventSource.close();
    console.log('SSE连接关闭');
  }
});
</script>

<style scoped>
.sse-demo {
  margin: 20px;
  padding: 20px;
  border: 1px solid #eee;
  border-radius: 8px;
  line-height: 1.8;
}
</style>

方式2:封装成工具类(全局复用)

创建src/utils/sse.js,封装SSE的初始化、监听、关闭方法:

javascript 复制代码
/**
 * SSE工具类(Vue3全局复用)
 * @param {String} url - SSE服务端接口地址
 * @param {Function} onMessage - 接收数据的回调函数
 * @param {Function} onError - 错误回调函数
 * @returns {Object} - 包含close方法的对象
 */
export const createSSE = (url, onMessage, onError) => {
  if (!window.EventSource) {
    alert('当前浏览器不支持SSE,请更换浏览器!');
    return null;
  }

  const eventSource = new EventSource(url);

  // 连接成功
  eventSource.onopen = () => {
    console.log('SSE连接成功');
  };

  // 接收数据
  eventSource.onmessage = (e) => {
    onMessage && onMessage(e.data);
  };

  // 连接错误
  eventSource.onerror = (err) => {
    console.error('SSE连接异常:', err);
    onError && onError(err);
    // 自动重连
    eventSource.close();
    setTimeout(() => createSSE(url, onMessage, onError), 3000);
  };

  // 关闭SSE连接
  const closeSSE = () => {
    if (eventSource) {
      eventSource.close();
      console.log('SSE连接关闭');
    }
  };

  return { closeSSE };
};
    

在组件中使用工具类:

xml 复制代码
<template>
  <div class="sse-demo">
    <h3>SSE实时时间推送(工具类复用版)</h3>
    <p>当前服务端时间:{{ serverTime }}</p>
  </div>
</template>

<script setup>
import { ref, onUnmounted } from 'vue';
import { createSSE } from '@/utils/sse'; // 引入封装的SSE工具类

const serverTime = ref('');
let sseInstance = null;

// 初始化SSE连接(调用工具类)
const initSSE = () => {
  // 替换为自己的服务端接口地址(Java或Node均可)
  const sseUrl = 'http://localhost:8080/sse/java/stream';
  // const sseUrl = 'http://localhost:3001/sse/node/stream';

  sseInstance = createSSE(
    sseUrl,
    // 接收服务端推送数据的回调
    (data) => {
      serverTime.value = data; // 将推送的时间赋值给页面变量
    },
    // 连接异常的回调
    (err) => {
      console.error('SSE连接出现异常,已自动重连', err);
    }
  );
};

// 组件挂载时初始化SSE
initSSE();

// 组件卸载时关闭SSE连接,避免内存泄漏(关键步骤)
onUnmounted(() => {
  if (sseInstance) {
    sseInstance.closeSSE(); // 调用工具类的关闭方法
  }
});
</script>

<style scoped>
.sse-demo {
  margin: 20px;
  padding: 20px;
  border: 1px solid #eee;
  border-radius: 8px;
  line-height: 1.8;
}
</style>

三、SSE vs WebSocket(必看选型指南)

很多人纠结什么时候用SSE,什么时候用WebSocket,直接看对比表,一目了然:

特性 SSE WebSocket
通信方向 单向(服务端→客户端) 双向(服务端↔客户端)
底层协议 基于HTTP(GET请求) 独立协议(ws:// / wss://)
浏览器支持 原生支持(EventSource),IE不支持 大部分浏览器支持,需用API调用
自动重连 ✅ 原生内置,无需手动实现 ❌ 需手动实现心跳、重连逻辑
数据格式 仅支持文本(需自行序列化JSON) 支持文本、二进制(如图片、文件)
开发成本 低(无需处理握手、帧解析) 高(需处理握手、断线重连、帧解析)
部署成本 低(复用HTTP端口,防火墙友好) 高(需单独配置端口、处理代理)
适用场景 单向推送(AI流式输出、实时通知、行情监控、日志输出) 双向交互(聊天、实时协作、游戏、弹幕)

选型建议(一句话总结)

如果你的需求是「服务端推数据,客户端只接收」,用SSE(开发快、部署简单);如果需要「客户端和服务端互相通信」,用WebSocket(功能强、灵活)。

四、常见问题&避坑指南

1. 客户端无法接收数据?

  • 检查服务端响应头是否正确设置Content-Type: text/event-stream

  • 检查数据格式是否为data: 内容\n\n(两个换行符不可少);

  • 检查跨域配置(服务端需设置Access-Control-Allow-Origin)。

2. 连接频繁断开?

  • 服务端需避免数据缓存,确保Cache-Control: no-cache

  • 若服务端长时间无数据推送,可定期推送空数据(data: \n\n),保持连接;

  • 检查服务器是否有超时断开长连接的配置(如Nginx的proxy_read_timeout)。

3. 浏览器不支持SSE?

  • IE浏览器不支持EventSource,可使用第三方polyfill(如event-source-polyfill);

  • 移动端浏览器(Chrome、Safari)均支持,无需额外处理。

五、总结

SSE作为一种轻量、简单的实时推送技术,在单向数据流场景中,比WebSocket更具优势------开发快、部署简单、无需处理复杂的双向通信逻辑。

本文从原理到实战,覆盖了Java、Node.js服务端,以及Vue客户端的完整示例,复制代码就能跑通。下次再遇到实时推送需求,不用再纠结,根据是否需要双向通信,直接选择SSE或WebSocket即可。

欢迎点赞、收藏、转发,关注我。

相关推荐
xiaoxiaoyu661 小时前
简历PDF解析,我一个学生被两栏布局整不会了
java
zlpzlpzyd1 小时前
slf4j中jcl-over-slf4j、jul-to-slf4j、log4j-over-slf4j、slf4j-api的区别是什么
java·开发语言·log4j
贺国亚1 小时前
线程基础与生命周期- 并发编程
java·后端
人道领域1 小时前
【LeetCode刷题日记】222.极速计算完全二叉树节点数:O(log²n)算法揭秘
java·数据结构·算法·leetcode·深度优先
传说之后1 小时前
Go语言并发安全入门指南
后端
MacroZheng1 小时前
IDEA + Claude Code = 王炸!
人工智能·后端·intellij idea
Solis1 小时前
高性能二级缓存设计:Caffeine + 滑动窗口热点降级方案
后端