Skip to content

消息推送——SSE(Server-Sent Events)详解与实战

约 2057 字大约 7 分钟

javascript消息推送

2025-04-28

消息推送——SSE(Server-Sent Events)详解与实战

​ 在现代 Web 开发中,实时通信已经成为众多应用的核心需求之一。从聊天室到股票行情更新,从通知系统到物联网数据展示,我们常常需要服务器将数据“推”给客户端。

​ 虽然 WebSocket 是一种强大的双向通信协议,但在很多场景下,我们只需要 服务器向客户端的单向推送 功能即可。这时候,一种轻量级、高效的替代方案 —— SSE(Server-Sent Events) 就派上了用场。

一、什么是 SSE?

SSE(Server-Sent Events) 是 HTML5 提供的一种浏览器与服务器之间的通信技术,允许服务器向浏览器主动推送信息。它基于标准的 HTTP 协议,实现简单,适合于服务器频繁或持续地向客户端发送数据的场景。

✅ 特点总结:

特性描述
单向通信仅支持服务器向客户端推送消息
基于 HTTP使用普通的 HTTP/HTTPS 协议
自动重连客户端自动尝试重新连接
轻量高效数据格式为文本,解析容易
支持事件类型可以定义多个事件名(如 messageupdatenotification 等)

二、SSE 的基本使用

1. 前端代码(JavaScript)

const eventSource = new EventSource('http://your-api.com/sse');

// 监听默认的 'message' 事件
eventSource.onmessage = function(event) {
    console.log("收到消息:", event.data);
};

// 监听自定义事件
eventSource.addEventListener('notification', function(event) {
    console.log("收到通知:", event.data);
});

// 错误处理
eventSource.onerror = function(err) {
    console.error("发生错误:", err);
};

⚠️ 注意:前端不能通过 JavaScript 主动向服务端发送消息(SSE 不支持客户端发送),这是与 WebSocket 的一个重要区别。


2. 后端代码(Spring Boot Java 示例)

使用 SseEmitter

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

@RestController
public class SseController {

    @GetMapping(value = "/sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter createConnection() {
        SseEmitter emitter = new SseEmitter(60_000L); // 设置超时时间为 60 秒

        new Thread(() -> {
            try {
                // 发送一条普通 message 类型的消息
                emitter.send("Hello, this is a message.");

                // 发送一条自定义事件类型的通知
                emitter.send(SseEmitter.event()
                        .name("notification")
                        .data("You have a new notification!"));

                emitter.complete(); // 完成连接
            } catch (IOException e) {
                emitter.completeWithError(e);
            }
        }).start();

        return emitter;
    }
}

三、SSE 的工作原理

SSE 的通信过程如下:

  1. 客户端使用 EventSource(url) 创建一个与服务器的连接。
  2. 服务器响应头设置为 Content-Type: text/event-stream,表示这是一个流式响应。
  3. 服务器保持连接打开,并可以随时向客户端发送数据。
  4. 客户端监听事件并作出响应。
  5. 如果连接中断,客户端会自动尝试重新连接(除非明确关闭)。

📈 典型的数据格式示例:

data: This is a simple message.

event: notification
data: {"title": "New Message", "content": "You've got mail!"}

四、SSE vs WebSocket

对比项SSEWebSocket
协议HTTPTCP
通信方向单向(服务器 → 客户端)双向
是否自动重连✅ 是❌ 需手动实现
实现复杂度⭐ 简单⭐⭐⭐ 复杂
浏览器兼容性广泛支持(IE 不支持)Chrome/Firefox/Edge 支持良好
适用场景推送为主(如通知、日志、仪表盘)实时交互(如聊天、游戏、协同编辑)

五、SSE 应用场景

  • 实时通知系统(如邮件、订单提醒)
  • 数据仪表盘(股票价格、CPU 使用率等)
  • 日志查看工具(远程日志输出到前端页面)
  • 表单协同预览 / 编辑
  • 游戏排行榜更新推送
  • IoT 设备状态监控

六、进阶技巧:多客户端管理 + 异步推送

当有多个用户连接 SSE 接口时,通常我们需要一个全局的 Map<String, SseEmitter> 来保存所有连接,并根据业务逻辑向特定用户发送消息。

image-20250429161111198

前端代码

前端js代码,前端客户端通过输入clinetId,调用createSSEClient方法创建SSE连接。在输入框中输入输入自定义客户端id,发送请求,后端会创建一个SseEmitter实例。

<script lang="ts" setup >
   const data = ref('')
   const messageList = ref<string[]>([])
   const message = ref('')
      /**
   * 创建sse客户端
   */
  function createSSEClient(clientId: string) {
    var sseSource = new EventSource("http://localhost:9901/message/connect?clientId="+clientId);

    // 连接打开
    sseSource.onopen = function () {
      ElMessage.success("sse连接成功:" + cilendId.value);
        console.log("连接打开");
    }
    // 连接错误
    sseSource.onerror = function (err) {
      ElMessage.error("sse连接失败:"+ cilendId.value);
      console.log("连接失败",err );
    }
    // 接收到数据
    sseSource.onmessage = function (event) {
      console.log("接收到数据data:", event.data);
      data.value = event.data
      messageList.value.push(data.value)
      console.log("转换的数据",data.value)
      handleReceiveData(event.data)
    }
  }

    // 处理服务器返回的数据
    function handleReceiveData(data:string) {
       //设置消息处理策略 
    }

	
    // 通过http发送消息
    function sendMessage() {
      console.log("发送消息")
      const data = {
          message: message.value,
          clientId:cilendId.value
      }
      fetch('http://localhost:9901/message/sendMessage', {
          method: 'POST',
          headers: {
              'Content-Type': 'application/json;charset=UTF-8'
          },
          body: JSON.stringify(data)
      })
  }
</script>

后端代码

Service服务端代码,在实际使用中用户可以根据cilentId区分选择响应的方法处理并响应消息。

@Service
public class SseService {

    /**
     * k:客户端id  v:SseEmitter
     */
    private static Map<String, SseEmitter> sseEmitterMap = new ConcurrentHashMap<>();

    public SseEmitter connect(String clientId) {
        //6小时后断开
        SseEmitter sseEmitter = new SseEmitter(6 * 60 * 60 * 1000L);
        if (sseEmitterMap.containsKey(clientId)){
            throw  new BusinessException("该clientId已绑定指定的客户端!clientId:" + clientId);
        }
        if (sseEmitterMap.size() > 1000) {
            throw  new BusinessException("当前连接数已满!");
        }
        try {
            sseEmitter.send(""+clientId+""+"连接成功", MediaType.APPLICATION_JSON);
        } catch (IOException e) {
            log.error("sse连接发送数据时出现异常:", e);
            throw new BusinessException("sse连接发送数据时出现异常!");
        }
        // 连接报错
        sseEmitter.onError((throwable) -> {
            log.info("sse连接异常:", throwable);
            sseEmitterMap.remove(clientId);
        });
        sseEmitterMap.put(clientId, sseEmitter);
        return sseEmitter;
    }
    
        public void activeSendMessage(SseMessage message) {
        if (ObjectUtil.isEmpty(sseEmitterMap)) {
            return;
        }
        if (ObjectUtil.isEmpty(message.getClientId())){
            return;
        }
        SseEmitter sseEmitter1 = sseEmitterMap.get(message.getClientId());
        if (ObjectUtil.isNotEmpty(sseEmitter1)){
            try {
                sseEmitter1.send(""+message.getClientId() + "】发送消息:" + message.getMessage(), MediaType.APPLICATION_JSON);
            } catch (IOException e) {
                log.error("sse连接发送数据时出现异常:", e);
            }
        }
       }
}

七、SSE事件监听

在前端 JavaScript 中,我们通过 EventSource 对象来与服务器建立连接,并监听其发送过来的消息。以下是几种主要的事件监听方式


//1. 监听默认的 `message` 事件: 当服务器发送的数据没有指定特定的事件名时,默认会触发 `message` 事件。
const eventSource = new EventSource('/sse-endpoint');

eventSource.onmessage = function(event) {
    console.log("收到默认消息:", event.data);
};

//2. 使用 `addEventListener` 监听特定事件: 如果服务器发送的数据指定了事件名称(即 `event` 字段),你可以通过 `addEventListener` 方法来监听这些特定事件。
const eventSource = new EventSource('/sse-endpoint');
// 监听名为 'customEvent' 的事件
eventSource.addEventListener('customEvent', function(event) {
    console.log("收到自定义事件: ", event.data);
});

//3.错误处理: 为了确保应用能够正确处理网络中断或其他错误情况,添加一个错误处理器是很重要的。
eventSource.onerror = function(error) {
    console.error("发生错误: ", error);
};

事件监听示例

image-20250429161903790

前端代码

<script lang="ts" setup >
  const eventList = ref<string[]>([])
    
  function eventSSE(event:string){ 
    const sse = new EventSource('http://localhost:9901/message/event?eventName='+event)
    sse.addEventListener('open', (e) => {
        console.log("edit",e.target)
    })
    /**
     * 监听message事件:message是默认事件,没有指定监听的主题时,会默认发送一个message事件
     */
    sse.addEventListener(event, (e) => {
        eventList.value.push(e.data)
        console.log("edit2",e.data)
    })
  }
</script>

后端代码

    /**
     * SSE事件监听
     * @return
     */
    @GetMapping(path = "/event", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter event(@RequestParam @NotNull(message = "eventName不能为空") String eventName) {
        log.info("sse实时推送新用户连接:{}", eventName);
        SseEmitter emitter = new SseEmitter();
        threadPoolExecutor.execute(() -> {
            try {
                for (int i = 0; i < 50; i++) {
                    emitter.send(SseEmitter.event().name(eventName).data(""+ eventName+ "】:" + i));
                    Thread.sleep(3000);
                }
                emitter.complete();
            }catch (Exception e){
                emitter.completeWithError(e);
            }
        });
        return emitter;
    }

八、常见问题和注意事项

✅ 如何避免连接被浏览器或服务器中断?

  • 设置合理的超时时间(例如 new SseEmitter(60_000L));
  • 在后端定时发送心跳/空消息防止连接闲置断开。

✅ 如何保证消息顺序性和可靠性?

  • SSE 本身不提供复杂的 QoS 机制(如重传、确认),如果需要更强可靠性,建议结合数据库记录+重试策略。

✅ 如何测试 SSE 接口?

  • 使用curl 查看原始输出:

    curl http://localhost:8080/sse
  • 或使用 Postman,选择 Get 请求并设置接受类型为 text/event-stream


九、总结

​ SSE 是一种轻量级、易于实现的服务器到客户端的实时通信方式,特别适合那些不需要全双工通信的业务场景。相比 WebSocket,它更简单、更易维护,在大多数情况下已经足够满足需求。


微信公众号 作者微信

公众号二维码作者微信

鄂ICP备2025095751号-1