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
相关产品推荐
相关产品推荐

