【问题标题】:How does Server-Sent-Events work服务器发送事件如何工作
【发布时间】:2015-10-24 10:36:28
【问题描述】:

我在 tomcat 8.0 上使用 java 尝试了 SSE(服务器发送事件)。以下是我注意到的几件事。

我单击了一个按钮,该按钮自动向 servlet 发出请求。 Servlet 的 GET 方法被执行,该方法返回一个事件流。一旦接收到完整的流,页面再次自动发出另一个请求,再次接收相同的数据!!!我那里没有无限循环!!!

  1. 服务器上实际发生了什么?在正常情况下,tomcat 会创建一个线程来处理每个请求。现在发生了什么?

  2. 确保事件流只发送一次到同一个连接/浏览器会话的正确方法是什么?

  3. 确保事件流关闭且服务器上没有资源开销的正确方法是什么?

  4. 如何区分 GET 和 POST 请求。为什么选择 GET?

  5. 在 Tomcat 上使用 SSE 是否为时过早?有任何性能问题吗?

这里是好奇的代码,

@WebServlet("/TestServlet")
public class TestServlet extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        //content type must be set to text/event-stream
        response.setContentType("text/event-stream"); 
        //cache must be set to no-cache
        response.setHeader("Cache-Control", "no-cache");     
        //encoding is set to UTF-8
        response.setCharacterEncoding("UTF-8");

        PrintWriter writer = response.getWriter();

        for(int i=0; i<10; i++) {
            System.out.println(i);
            writer.write("data: "+ i +"\n\n");
            writer.flush();
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        writer.close(); 
    }
}

页面上的Javascript(页面上没有其他内容),

<button onclick="start()">Start</button>

<script type="text/javascript">
    function start() {
        var eventSource = new EventSource("TestServlet");
        eventSource.onmessage = function(event) {
            console.log("data: "+event.data)
            document.getElementById('foo').innerHTML = event.data;
        };
    }
</script>

使用 CURL 进行了尝试。回应只来了一次。我用的是chrome,所以这一定是chorme的问题??

编辑:

我学到的和学习的东西现在记录在我的博客中 - Server Sent Events

【问题讨论】:

  • 可能是您的浏览器。尝试使用 curl 发送初始请求,看看它是否仍然发生。
  • 是的,你是对的。 Curl 因一个请求而停止。
  • 我爱上了同一个...至少我能够为您节省时间和挫败感;)
  • @John 我认为浏览器没有问题。我从过去 6-8 个月开始使用这种想法,并且在生产中运行良好。检查您的脚本可能是 start() 被多次调用?
  • @Amogh 但这怎么可能呢?我评论了代码并放置了一个console.log,当我单击按钮时它只被调用一次。如果您已经使用了几个月,那么您可能会回答我的问题。

标签: java tomcat server-sent-events tomcat8


【解决方案1】:

改变这一行

writer.write("data: "+ i +"\n\n");

到

writer.write("data: "+ i +"\r\n");

顺便说一句,您的代码将有一个严重的性能问题,因为它会保留一个线程,直到发送所有事件。请改用异步处理 API。例如

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    AsyncContext actx = req.startAsync();
    actx.setTimeout(30*1000);
    //save actx and use it when we need sent data to the client.
}

那我们以后就可以使用AsyncContext了

//write some data to client when a certain event happens
actx.getResponse().getWriter().write("data: " + mydata + "\r\n");
actx.getResponse().getWriter().flush();

如果发送的所有事件我们都可以关闭它

actx.complete();

更新 1:

如果我们不希望浏览器在服务器完成响应后再次重新连接服务器,我们需要关闭浏览器的事件源。

eventSource.close();

另一种方法可能会有所帮助,即。我们设置了一个相当大的重试时间,但我没有尝试过,例如

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    AsyncContext actx = req.startAsync();
    actx.getResponse().getWriter().write("retry: 36000000000\r\n"); // 10000 hours!
    actx.getResponse().getWriter().flush();
    //save actx and use it when we need sent data to the client.
}

更新 2:

我认为 Websocket 可能更适合您的情况。

更新 3:(回答问题)

  1. 服务器上实际发生了什么?在正常情况下,tomcat 会创建一个线程来处理每个请求。现在发生了什么?

如果使用 Tomcat 8.0.X 中默认的 NIO 连接器,在整个处理周期内,关于请求的 HTTP I/O 不会持有线程。如果使用 BIO,则线程将被保留,直到整个处理周期完成。所有线程都来自一个线程池,tomcat不会为每个请求创建一个线程。

  1. 确保事件流只发送一次到同一个连接/浏览器会话的正确方法是什么?

在浏览器端做eventSource.close()是最好的选择。

  1. 什么是确保事件流关闭并且服务器上没有资源开销的正确方法?

不要忘记在服务器端调用 AsyncContext.complete()。

  1. 如何区分 GET 和 POST 请求。为什么选择 GET?

浏览器中的 EventSource API 只支持 GET 请求,但在服务器端没有这样的限制。 SSE 主要用于从服务器接收事件数据。如果事件发生,浏览器可以及时接收到它,而无需创建新的请求来轮询它。 如果您需要全双工通信,请尝试使用 SSE 的 WebSocket 插入。

  1. 在 Tomcat 上使用 SSE 是否为时过早?有任何性能问题吗?

如果我们使用 NIO 连接器和异步处理 API,应该不会出现性能问题。我不知道Tomcat NIO连接器是否成熟,但除非我们尝试一下,否则永远不会知道。

【讨论】:

  • 关闭不是比大重试时间更好的方法吗?不能发送 POST 请求吗?
  • 收盘比较好。关于 GET 或 POST 在服务器端没有限制。但是根据 [W3C SPEC](w3.org/TR/eventsource) 浏览器将始终使用 GET 而不是 POST 并且没有启用 POST 的选项。
  • 这就是答案。谢谢。
  • 根据规范:“如果连接关闭,客户端将重新连接;可以使用 HTTP 204 No Content 响应代码告诉客户端停止重新连接。”我一直在自己研究 SSE 的 Java impl,你可以在这里查看:github.com/metteo/event-source
  • Spring Boot 在控制器上返回 SseEmitter 时具有服务器发送事件。
【解决方案2】:

我强烈建议您先阅读Stream Updates with Server-Sent Events,以便对该技术有一个很好的总体了解。 然后关注Server-Sent Events with Async Servlet By Example,了解如何将 SSE 专门用于 Servlet 技术。

【讨论】:

  • Wtf 是在第二个链接上吗?写这个的人相信这个代码是人类可读的吗?如果是这样,而且他为 Oracle 工作,他应该被当场解雇。
  • @kuhaku 看起来底层博客引擎发生了一些改变,影响了代码格式——以前不是这样的(:
  • 第二个链接坏了,在waybackmachine找到它:web.archive.org/web/20150910123522/https://weblogs.java.net/…
【解决方案3】:

浏览器会在每次连接关闭大约 3 秒后尝试重新连接到源。您可以通过添加以“retry:”开头的行,后跟尝试重新连接之前等待的毫秒数来更改该超时。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-09-24
    • 1970-01-01
    • 2015-09-24
    • 2015-06-16
    • 2013-08-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多