服务器主动推送之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)

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

问:为啥要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...

相关推荐
无限大62 分钟前
为什么浏览器能看懂网页代码?——从HTML到渲染引擎的奇幻之旅
前端
福尔摩斯张4 分钟前
Linux信号捕捉特性详解:从基础到高级实践(超详细)
linux·运维·服务器·c语言·前端·驱动开发·microsoft
2401_860319526 分钟前
DevUI组件库实战:从入门到企业级应用的深度探索 ,如何快速安装DevUI
前端·前端框架
cc蒲公英27 分钟前
javascript有哪些内置对象
java·前端·javascript
a努力。34 分钟前
【基础数据篇】数据等价裁判:Comparer模式
java·后端
开心猴爷39 分钟前
苹果App Store应用程序上架方式全面指南
后端
zhangwenwu的前端小站42 分钟前
vue 对接 Dify 官方 SSE 流式响应
前端·javascript·vue.js
小飞Coding1 小时前
三种方式打 Java 可执行 JAR 包,你用对了吗?
后端
bcbnb1 小时前
没有 Mac,如何在 Windows 上架 iOS 应用?一套可落地的工程方案
后端
王林不想说话1 小时前
受控/非受控组件分析
前端·react.js·typescript