You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot中如何在服务端处理SseEmitter客户端连接关闭?

解决SSE客户端关闭后服务端无效连接的问题

你的代码已经做了基础的连接清理(onCompletion/onTimeout),但客户端主动关闭连接时,服务端并不会立刻收到通知,只有当服务端尝试向已断开的连接发送数据时才会触发异常。以下是针对性的优化方案:

1. 修复removeEmitter中的空指针风险

当前removeEmitter方法中,topicsEmitters.get(topic)可能返回null(比如并发场景下topic已被移除),直接调用contains会抛出空指针,先做非空判断:

public void removeEmitter(String topic, SseEmitter emitter) {
    CopyOnWriteArraySet<SseEmitter> emitters = this.topicsEmitters.get(topic);
    if (emitters != null && emitters.contains(emitter)) {
        emitters.remove(emitter);
        emitter.complete();
    }
    // 移除空的topic集合
    if (emitters != null && emitters.isEmpty()) {
        this.topicsEmitters.remove(topic);
    }
}

2. 完善发送事件时的异常处理

除了IOException,发送时还可能抛出其他异常(比如IllegalStateException,当Emitter已完成时),扩大异常捕获范围,确保所有失效连接都被清理:

@Override
public void dispatch(String topic, NotificationEvent event, Class<?> serializationView) {
    if (!this.topicsEmitters.containsKey(topic)) {
        return;
    }
    Set<SseEmitter> disabledEmitters = new HashSet<>();
    Set<SseEmitter> emitters = this.topicsEmitters.get(topic);
    for (SseEmitter emitter : emitters) {
        try {
            emitter.send(SseEmitter.event()
                    .name(event.getName())
                    .data(mapper.writerWithView(serializationView).writeValueAsString(event.getData()))
            );
        } catch (IOException | IllegalStateException e) {
            disabledEmitters.add(emitter);
        }
    }
    this.removeEmitters(topic, disabledEmitters);
}

3. 增加心跳机制检测存活连接

客户端关闭连接后,如果服务端长时间不发送数据,不会立刻触发异常。添加定时心跳,主动检测连接状态:
在SseService中添加心跳任务:

@Autowired
private TaskScheduler taskScheduler;

@PostConstruct
public void initHeartbeat() {
    // 每30秒发送一次心跳
    taskScheduler.scheduleAtFixedRate(() -> {
        for (String topic : this.topicsEmitters.keySet()) {
            Set<SseEmitter> emitters = this.topicsEmitters.get(topic);
            Set<SseEmitter> deadEmitters = new HashSet<>();
            for (SseEmitter emitter : emitters) {
                try {
                    // 发送空事件作为心跳
                    emitter.send(SseEmitter.event().comment("heartbeat"));
                } catch (Exception e) {
                    deadEmitters.add(emitter);
                }
            }
            this.removeEmitters(topic, deadEmitters);
        }
    }, 30000);
}

注意:Spring Boot默认会自动配置TaskScheduler,无需额外配置即可使用。

4. 优化Emitter的超时设置

你当前设置了SseEmitter(-1L)即永不超时,建议设置合理的超时时间,结合心跳机制避免无效连接长期占用资源:

// 设置3分钟超时,配合30秒一次的心跳,确保超时前能完成存活检测
SseEmitter emitter = new SseEmitter(180000L);

通过以上优化,服务端可以及时发现并清理客户端关闭的无效连接,避免向已断开的连接发送无用事件。

内容的提问来源于stack exchange,提问作者ant

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 07:23:11