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

相关推荐
杨DaB24 分钟前
【SpringMVC】拦截器,实现小型登录验证
java·开发语言·后端·servlet·mvc
奕辰杰2 小时前
关于npm前端项目编译时栈溢出 Maximum call stack size exceeded的处理方案
前端·npm·node.js
JiaLin_Denny4 小时前
如何在NPM上发布自己的React组件(包)
前端·react.js·npm·npm包·npm发布组件·npm发布包
路光.5 小时前
触发事件,按钮loading状态,封装hooks
前端·typescript·vue3hooks
我爱996!5 小时前
SpringMVC——响应
java·服务器·前端
咔咔一顿操作6 小时前
Vue 3 入门教程7 - 状态管理工具 Pinia
前端·javascript·vue.js·vue3
kk爱闹6 小时前
用el-table实现的可编辑的动态表格组件
前端·vue.js
漂流瓶jz7 小时前
JavaScript语法树简介:AST/CST/词法/语法分析/ESTree/生成工具
前端·javascript·编译原理
换日线°7 小时前
css 不错的按钮动画
前端·css·微信小程序
风象南7 小时前
前端渲染三国杀:SSR、SPA、SSG
前端