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

SseEmitter客户端断开时未抛出IOException问题排查

问题分析与解决方案

可能的原因及对应处理方式:

  • Tomcat输出缓冲导致连接断开检测延迟
    Tomcat默认会缓冲响应输出,只有当缓冲区填满(默认8KB)或显式调用flush时,才会将数据发送到客户端,此时才能检测到连接是否已断开。你当前的代码没有强制刷新缓冲区,所以即使客户端断开,只要缓冲区没满,send()方法不会立刻触发异常。
    处理方式:发送事件后手动获取响应输出流并flush,或者调整响应的缓冲区大小:

    try {
        emitter.send(SseEmitter.event()
                .id(event.getId().toString())
                .name("update")
                .data(data)
        );
        // 手动刷新响应缓冲区
        emitter.getResponse().getOutputStream().flush();
    } catch (Exception e) {
        logger.info("Dead emitter detected. Removing...", e); // 打印异常详情,确认类型
        deadEmitters.add(emitter);
    }
    
  • 未设置SseEmitter超时时间
    如果没有给SseEmitter设置超时,即使客户端长期断开连接,Emitter也会一直处于存活状态,不会触发任何异常。建议设置合理的超时时间,超时后会自动触发TimeoutException,你可以在异常处理中清理Emitter:

    SseEmitter emitter = new SseEmitter(30000L); // 设置30秒超时
    
  • Tomcat的sendfile机制干扰
    Tomcat的sendfile优化可能会绕过输出流的直接写入,导致连接断开无法被及时检测。可以通过禁用该机制来解决,在application.properties中添加:

    server.tomcat.sendfile-disable=true
    
  • 异常类型捕获不完整
    有时候Tomcat会将底层的IOException包装成NestedServletException(RuntimeException的子类),如果你的代码只捕获检查型Exception,可能会漏掉。建议捕获Throwable或者打印异常的实际类型,确认到底抛出了什么:

    catch (Throwable e) {
        logger.info("Dead emitter detected. Exception type: {}", e.getClass().getName(), e);
        deadEmitters.add(emitter);
    }
    

额外验证步骤:

  1. 测试时主动关闭客户端(比如浏览器标签页),然后立刻调用send(),观察是否触发异常。
  2. 查看Tomcat的访问日志,确认客户端断开时是否发送了FIN/RST包。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 15:50:17