java - Jersey 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/SocketException 异常。太完美了:我们捕获异常,将该调用者标记为不再连接,然后继续。

现在,问题来了。正如我所提到的,如果我们的远程组件软件未运行,或者运行它的计算机已关闭,则 eventOutput.write 会抛出异常。但是,在两种情况下,向不再连接的计算机调用 eventOutput.write 不会引发异常:1) 如果远程计算机的以太网电缆被简单地拔掉,而调用者是 SSE 连接的,以及 2) 如果当调用者连接到 SSE 时,远程计算机中的网络适配器已关闭(即通过管理操作)。在这两种情况下,我们可以在数小时内每五秒向远程计算机调用一次 eventOutput.write,并且不会抛出任何异常。这使得无法检测到远程计算机不再连接。

我看到 EventOutput(和 ChunkedOutput)的方法和属性很少,但我想知道是否有任何配置或使用它的方法会导致在写入已断开连接的远程计算机时抛出异常拔下以太网电缆或关闭网络适配器。

这是在 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 添加了可从套接字接口(interface)访问的所有信息,也无法通过围绕 SSE 套接字添加代码来解决所有可能的故障。唯一可行的解​​决方案是适当的双向通信。在 SSE 分块输出流的情况下,拉电缆不会导致任何中断,因为没有什么应该告诉它远程主机现在无法访问(直到操作系统关闭套接字)。

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

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

注意:浏览器擅长在网络中断的情况下自行重新建立 SSE 连接,无需摆弄。

关于java - Jersey Server-Sent Events - 写入断开的连接不会抛出异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28349585/

相关文章:

java - 我的无状态 session Bean 中不喜欢什么 Jersey?

java - 如何获取 Jersey JaxRS 中的所有查询参数?

java - Jersey XML 缩进

java - Swagger UI 导致 HTTP 406 对生成 json 以外的内容类型的操作 Not Acceptable 响应

java - Spring:如何针对多个数据源测试同一个类?

eclipse - 严重 : Servlet/JAXRS-HelloWorld threw load() exception java. lang.ClassNotFoundException : com. sun.jersey.spi.container.servlet.ServletContainer

java - Swagger 2 的多个资源包

java - 使用从扫描仪获取的字符串不起作用?

java - 在哪里查看哪个 JXBrowser 和 Selenium 匹配的版本?

java - Jersey 2 servlet 404 问题