服务器主动推送之SSE (Server-Sent Events)探讨

日期 更新说明
2025年7月29日 初版发布

前言

提到服务器主动推送消息给客户端方式,大家可能第一反应想到是WebSocket(当然你说客户端轮询也可行,只不过不是那么的优雅而已);而今天要提到的新的一种通信方式:SSE (Server-Sent Events)。尤其在当前AI大模型爆火的年代,LLM聊天机器人几乎已成成为日常必备的工具,其背后消息的推送主要即使基于SSE (Server-Sent Events)来实现消息推送的;如果你有兴趣让我们一起来探讨下这项技术背后的内容。

概述

是什么(What)

SSE (Server-Sent Events),服务器发送的事件: 通过 HTTP 为 Web 应用程序提供从服务器到客户端的事件流的异步通信。服务器可以向客户端发送非定向消息/事件,并且可以异步更新客户端,目前主流浏览器都支持该特性(IE 除外)。

为了便于理解,通俗一些理解,客户端和服务端建立链接以后,服务端可以向重复客户端发送数据流;可以理解成平时看视频,服务器可以提前推送给你推送流数据用于视频缓冲;只不过这个过程单工通信方式;下图方式便于理解,

为什么(Why)

既然有了WebSocket还要推出SSE (Server-Sent Events)呢?

SSE (Server-Sent Events)的本质是啥?有何特点,这里直接给你对比下区别:

Server-Sent Events WebSocket
通信模式 单向通道(HTTP长连接) 双向实时通道独立协议
开发实现 简单,浏览器原生支持,服务端发送Content-Type: text/event-stream响应头 复杂,需在客户端/服务端分别实现握手、帧解析、心跳维持等逻辑
文本格式 文本 文本和二进制

其实简单的总结一下,如果你刚好需要服务主动推送数据的,如果追求强交互、强实时、协议定制化建议使用WebSocke,如果当初需要服务推送数据的话使用Server-Sent Events是个优雅的选择。

实践(How)

下面将演示服务端每个一秒中发送带时间的消息给浏览器(客户端),我们分别会使用 Java和Rust分别演示,先看效果:

问:为啥要rust演示 SSE呢?

答:作者在学 元神语言:rust

  1. 浏览器编码:
html 复制代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>SSE handler Rust</title>
    <meta name="description" content="">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="">
</head>
<body>
<!--[if lt IE 7]>
<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="#">upgrade your browser</a> to improve your experience.</p>
<![endif]-->

<h1>Server Sent Events Power By Rust</h1>
<div id="con"></div>
<script lang="javascript">
    let chat = document.getElementById("con");
    var source = new EventSource("/events");
    source.onmessage = function(event) {
        chat.innerHTML += event.data + '<br/>';
        console.log("Got:", event.data);
    };
</script>
</body>
</html>
  1. 服务端
java 复制代码
SseEmitter sseEmitter = sseEmitterMap.get(uid);
    if (sseEmitter == null) {
        log.info("消息推送失败uid:[{}],没有创建连接,请重试。", uid);
        return false;
    }
    try {
        sseEmitter.send(SseEmitter.event().id(messageId).reconnectTime(Duration.ofSeconds(1).toMillis()).data(message));
        log.info("用户{},消息id:{},推送成功:{}", uid, messageId, message);
        return true;
    } catch (Exception e) {
        sseEmitterMap.remove(uid);
        log.info("用户{},消息id:{},推送异常:{}", uid, messageId, e.getMessage());
        sseEmitter.complete();
        return false;
    }

Java 部分只需要引入 ·SpringMvc·其实可以了

rust 复制代码
    TypedHeader(user_agent): TypedHeader<headers::UserAgent>,
) -> Sse<impl Stream<Item = Result<Event, Infallible>>> {
    info!("`{}` connected", user_agent.as_str());

    // A `Stream` that repeats an event every second
    //
    // You can also create streams from tokio channels using the wrappers in
    // https://docs.rs/tokio-stream
    let stream = stream::repeat_with(|| {
        let data = format!("hi! now:{}", Local::now().to_string());
        info!("send message: [{}]", data);
        Event::default().data(data)
    })
        .map(Ok)
        .throttle(Duration::from_secs(1));

    Sse::new(stream).keep_alive(
        axum::response::sse::KeepAlive::new()
            .interval(Duration::from_secs(1))
            .text("keep-alive-text"),
    )

rust 需要引入axum

rust 的 SSE 案例工程链接:github.com/will-we/blo...

Java的 SSE 案例工程链接:github.com/will-we/blo...

相关推荐
java1234_小锋1 小时前
【技术专题】HTML5 网页技术 - HTML5 Canvas 基础以及动画入门
前端·html·html5
知了学历孙老师4 小时前
小自考的校考和统考,出题方和难度差在哪
java·大数据·前端
Java爱好狂.5 小时前
Java初学者如何一周速通SpringBoot!
java·spring boot·后端·spring·高并发·架构师·java面试
ShineWinsu6 小时前
对于JavaScript:操作元素的解析
开发语言·前端·javascript·面试·web·dom·元素
马良神笔7 小时前
2026 年Rag最近技术前沿总结
后端·python·django
程序猿DD7 小时前
同样10美元,OpenDesign和Command Code的DeepSeek V4.1 Flash额度到底有多少?
后端
福兮说7 小时前
JSON 格式正确,数据就可靠吗?我用一份自编配置检查类型与空值
java·前端·json
高频因子挖掘机7 小时前
A股量化从日线升级到5分钟线:数据量增加后最先暴露哪些工程问题?
后端·github·api
高频因子挖掘机7 小时前
历史行情返回顺序变了,为什么按行号计算指标很危险?
后端·github·api
程序员老赵9 小时前
Docker 部署青龙面板:轻松搭建定时任务管理平台
前端·docker·开源