Java Jersey服务器已发送事件-写入断开的连接不会引发异常

Java Jersey服务器已发送事件-写入断开的连接不会引发异常,java,jersey,jersey-2.0,server-sent-events,Java,Jersey,Jersey 2.0,Server Sent Events,我们使用Jersey Server Sent Events(SSE)允许应用程序的远程组件侦听Jersey/Tomcat服务器引发的事件。这很有效 但是,我们的服务器必须有当前连接的侦听器(我们的远程组件)的准确列表。为此,我们的服务器每五秒钟向每个调用方发送一条小消息(通过eventOutput.write)。如果远程组件在SSE连接时关闭,或者远程计算机在SSE连接时关闭电源,则服务器的eventOutput.write会引发如下所示的ClientAbortException/SocketE

我们使用Jersey Server Sent Events(SSE)允许应用程序的远程组件侦听Jersey/Tomcat服务器引发的事件。这很有效

但是,我们的服务器必须有当前连接的侦听器(我们的远程组件)的准确列表。为此,我们的服务器每五秒钟向每个调用方发送一条小消息(通过eventOutput.write)。如果远程组件在SSE连接时关闭,或者远程计算机在SSE连接时关闭电源,则服务器的eventOutput.write会引发如下所示的ClientAbortException/SocketException异常。这很完美:我们捕获异常,将该调用方标记为不再连接,然后继续

现在,为了这个问题。正如我提到的,如果我们的远程组件软件没有运行,或者它运行的计算机断电,eventOutput.write会抛出异常。但是,在两种情况下,调用eventOutput.write到不再连接的计算机不会引发异常:1)如果在调用方连接SSE时简单拉动远程计算机的以太网电缆,以及2)如果远程计算机中的网络适配器关闭(即通过管理操作)当呼叫方已连接时。在这两种情况下,我们可以在数小时内每隔五秒调用eventOutput.write到远程计算机,并且不会引发异常。这使得无法检测到远程计算机不再连接

我看到EventOutput(和ChunkeOutput)只有很少的方法和属性,但我想知道是否有任何方法可以配置或使用它,从而在写入远程计算机时引发异常,该远程计算机已通过拉以太网电缆或关闭网络适配器断开连接

下面是在eventOutput.write确实引发我们想要的异常的情况下得到的(良好/有用)异常:

org.apache.catalina.connector.ClientAbortException: null
    at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:371) ~[catalina.jar:7.0.53]
    at org.apache.catalina.connector.OutputBuffer.flush(OutputBuffer.java:333) ~[catalina.jar:7.0.53]
    at org.apache.catalina.connector.CoyoteOutputStream.flush(CoyoteOutputStream.java:101) ~[catalina.jar:7.0.53]
    at org.glassfish.jersey.servlet.internal.ResponseWriter$NonCloseableOutputStreamWrapper.flush(ResponseWriter.java:303) ~[jaxrs-ri-2.13.jar:2.13.]
    at org.glassfish.jersey.message.internal.CommittingOutputStream.flush(CommittingOutputStream.java:292) ~[jaxrs-ri-2.13.jar:2.13.]
    at org.glassfish.jersey.server.ChunkedOutput$1.call(ChunkedOutput.java:240) ~[jaxrs-ri-2.13.jar:2.13.]
    at org.glassfish.jersey.server.ChunkedOutput$1.call(ChunkedOutput.java:190) ~[jaxrs-ri-2.13.jar:2.13.]
    at org.glassfish.jersey.internal.Errors.process(Errors.java:315) ~[jaxrs-ri-2.13.jar:2.13.]
    at org.glassfish.jersey.internal.Errors.process(Errors.java:242) ~[jaxrs-ri-2.13.jar:2.13.]
    at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:347) ~[jaxrs-ri-2.13.jar:2.13.]
    at org.glassfish.jersey.server.ChunkedOutput.flushQueue(ChunkedOutput.java:190) ~[jaxrs-ri-2.13.jar:2.13.]
    at org.glassfish.jersey.server.ChunkedOutput.write(ChunkedOutput.java:180) ~[jaxrs-ri-2.13.jar:2.13.]
    at com.appserver.webservice.AgentSsePollingManager$ConnectionChecker.run(AgentSsePollingManager.java:174) ~[AgentSsePollingManager$ConnectionChecker.class:na]
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471) [na:1.7.0_71]
    at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:304) [na:1.7.0_71]
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:178) [na:1.7.0_71]
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) [na:1.7.0_71]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [na:1.7.0_71]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [na:1.7.0_71]
    at java.lang.Thread.run(Thread.java:745) [na:1.7.0_71]
Caused by: java.net.SocketException: Broken pipe
    at java.net.SocketOutputStream.socketWrite0(Native Method) ~[na:1.7.0_71]
    at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:113) ~[na:1.7.0_71]
    at java.net.SocketOutputStream.write(SocketOutputStream.java:159) ~[na:1.7.0_71]
    at org.apache.coyote.http11.InternalOutputBuffer.realWriteBytes(InternalOutputBuffer.java:215) ~[tomcat-coyote.jar:7.0.53]
    at org.apache.tomcat.util.buf.ByteChunk.flushBuffer(ByteChunk.java:480) ~[tomcat-coyote.jar:7.0.53]
    at org.apache.coyote.http11.InternalOutputBuffer.flush(InternalOutputBuffer.java:119) ~[tomcat-coyote.jar:7.0.53]
    at org.apache.coyote.http11.AbstractHttp11Processor.action(AbstractHttp11Processor.java:799) ~[tomcat-coyote.jar:7.0.53]
    at org.apache.coyote.Response.action(Response.java:174) ~[tomcat-coyote.jar:7.0.53]
    at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:366) ~[catalina.jar:7.0.53]
    ... 19 common frames omitted

我认为,即使Jersey添加了可从套接字接口访问的所有信息,也不可能通过在SSE套接字周围添加代码来解释所有可能的故障。唯一可行的解决方案是适当的双向沟通。在SSE分块输出流的情况下,拉动的电缆不会导致任何中断,因为不应该告诉它远程主机现在无法访问(直到操作系统关闭套接字)

你的第一步是正确的——每N秒执行一次心跳。然后,您所需要做的就是每隔一段时间用另一个小http调用进行报告,这样您仍然可以收听。这取决于你每5秒或每分钟做一次确认-取决于你需要多快的问题检测

您可以通过实现@POST在同一Jersey资源中实现它(在RESTful术语中,它读作“创建接收事件的新确认”)


注意:浏览器擅长于在网络中断的情况下自行重新建立SSE连接,无需费心处理

多谢。是的,客户端心跳消息是一种解决方法。但是,关于SSE写入,似乎通过TCP发送的任何数据都应该产生确认或失败。下面的文章很好地解释了我们的“半开放连接”情况(例如,未拔出的网络电缆)。它断言这种情况总是可以通过连接上的写操作检测到的。(阅读第三段)这意味着Jersey应该能够识别写入故障,即使是在网络电缆被拉的情况下。