服务器推送事件SSE

SSE介绍

SSE 通常指的是 Server-Sent Events(服务器推送事件) ,是一种服务器 → 浏览器 的实时通信技术,基于 HTTP,是单向的文本流推送,不支持推送二进制

通俗理解: 浏览器发起一次 HTTP 请求,服务器可以持续不断地往浏览器推送数据

对比WebSocket

对比项 SSE WebSocket
通信方向 单向 双向
协议 HTTP WS / WSS
浏览器支持 原生 原生
自动重连 自动支持 (需自己实现)
适合场景 消息通知、进度条 聊天、游戏

SSE适合的场景

  • 导入 / 导出进度通知

  • AI / 大模型流式返回

  • 长任务进度推送

  • 实时日志推送

  • 状态变更通知

SSE不适用的场景

  • 需要客户端频繁发消息(如聊天)

  • 需要二进制数据

  • 需要强实时双向交互

SSE实现

统一推送模型

java 复制代码
package com.example.ssedemo.model;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * SSE 推送消息统一模型
 */
public class SseMessage implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 消息类型
     * connect / ping / progress / success / error / notice
     */
    private String type;

    /**
     * 消息内容
     */
    private String content;

    /**
     * 扩展数据(进度、结果等)
     */
    private Object data;

    /**
     * 发送时间
     */
    private LocalDateTime time;

    public SseMessage() {
    }

    public SseMessage(String type, String content) {
        this.type = type;
        this.content = content;
        this.time = LocalDateTime.now();
    }

    public SseMessage(String type, String content, Object data) {
        this.type = type;
        this.content = content;
        this.data = data;
        this.time = LocalDateTime.now();
    }

    // ===== getter / setter =====

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public LocalDateTime getTime() {
        return time;
    }

    public void setTime(LocalDateTime time) {
        this.time = time;
    }
}

控制器接口

java 复制代码
package com.example.ssedemo.controller;

import com.example.ssedemo.model.SseMessage;
import com.example.ssedemo.service.SseClientManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

@RestController
@RequestMapping("/sse")
public class SseController {

    @Autowired
    private SseClientManager sseClientManager;

    /**
     * 建立 SSE 连接
     */
    @GetMapping(value = "/connect", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter connect(@RequestParam String clientId) {
        return sseClientManager.connect(clientId);
    }

    /**
     * 给指定客户端推送
     */
    @PostMapping("/send")
    public void send(@RequestParam String clientId,
                     @RequestParam String message) {

        SseMessage msg = new SseMessage(
                "notice",
                message
        );
        sseClientManager.send(clientId, msg);
    }

    /**
     * 广播
     */
    @PostMapping("/broadcast")
    public void broadcast(@RequestParam String message) {

        SseMessage msg = new SseMessage(
                "notice",
                message
        );
        sseClientManager.broadcast(msg);
    }

    /**
     * 在线人数
     */
    @GetMapping("/online")
    public int online() {
        return sseClientManager.onlineCount();
    }
}

核心Client管理

java 复制代码
package com.example.ssedemo.service;

import com.example.ssedemo.model.SseMessage;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Service
public class SseClientManager {

    /** 保存所有在线客户端 */
    private final Map<String, SseEmitter> clients = new ConcurrentHashMap<>();

    /**
     * 建立连接
     */
    public SseEmitter connect(String clientId) {
        // 不超时
        SseEmitter emitter = new SseEmitter(0L);

        clients.put(clientId, emitter);

        // 连接完成 / 超时 / 异常时清理
        emitter.onCompletion(() -> remove(clientId));
        emitter.onTimeout(() -> remove(clientId));
        emitter.onError(e -> remove(clientId));

        return emitter;
    }

    /**
     * 移除客户端
     */
    public void remove(String clientId) {
        clients.remove(clientId);
    }

    /**
     * 给指定客户端发送消息
     */
    public void send(String clientId, SseMessage message) {
        SseEmitter emitter = clients.get(clientId);
        if (emitter == null) {
            return;
        }
        try {
            emitter.send(SseEmitter.event()
                    .name(message.getType())
                    .data(message));
        } catch (Exception e) {
            remove(clientId);
        }
    }


    /**
     * 广播
     */
    public void broadcast(SseMessage message) {
        clients.forEach((clientId, emitter) -> {
            try {
                emitter.send(message);
            } catch (Exception e) {
                remove(clientId);
            }
        });
    }

    /**
     * 在线人数
     */
    public int onlineCount() {
        return clients.size();
    }
}

SSE测试

建立客户端连接

http://localhost:9800/sse/connect?clientId=test001

推送消息

http://localhost:9800/sse/send

验证推送

相关推荐
05大叔3 分钟前
网络基础知识 域名,JSON格式,AI基础
运维·服务器·网络
安当加密6 分钟前
无需改 PAM!轻量级 RADIUS + ASP身份认证系统 实现 Linux 登录双因子认证
linux·运维·服务器
dashizhi20157 分钟前
服务器共享禁止保存到本地磁盘、共享文件禁止另存为本地磁盘、移动硬盘等
运维·网络·stm32·安全·电脑
卷福同学43 分钟前
【养虾日记】QClaw操作浏览器自动化发文
运维·人工智能·程序人生·自动化
woho7788992 小时前
不同网段IP的网络打印机,打印、扫描设置
运维·服务器·网络
耗子会飞2 小时前
小白学习固定VM虚拟机的centos服务器的IP
运维·服务器·centos
门豪杰2 小时前
Ubuntu下安装Claude Code
linux·运维·ubuntu·claude·claude code
新新学长搞科研3 小时前
第五届电子、集成电路与通信技术国际学术会议(EICCT 2026)
运维·人工智能·自动化·集成测试·信号处理·集成学习·电气自动化
桌面运维家3 小时前
Windows/Linux双启动:BIOS/UEFI多配置桌面创建指南
linux·运维·windows
無法複制3 小时前
debian安装Postgresql-14.x
运维·postgresql·debian