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

相关推荐
Mapmost1 分钟前
让 AI 真正看懂世界—构建具备空间理解力的智能体
前端
踏浪无痕3 分钟前
Maven父子模块Deploy的那些坑
后端·maven
bcbnb7 分钟前
iOS 应用上架全流程实战解析,从证书到审核的完整开发者指南
后端
橙 子_18 分钟前
我本以为代码是逻辑,直到遇见了HTML的“形”与“意”【一】
前端·html
Kisang.23 分钟前
【HarmonyOS】ArkWeb——从入门到入土
前端·华为·typescript·harmonyos·鸿蒙
沉默璇年29 分钟前
tgz包批量下载脚本
前端
master_hl34 分钟前
基于 Kafka 与时间轮实现高性能延迟消息
后端
a***13141 小时前
python的sql解析库-sqlparse
android·前端·后端
s***55811 小时前
Skywalking介绍,Skywalking 9.4 安装,SpringBoot集成Skywalking
spring boot·后端·skywalking
0***R5151 小时前
前端构建工具缓存,node_modules
前端·缓存