Server-Sent-Events如何运作

时间:2015-08-02 03:43:42

标签: java tomcat server-sent-events tomcat8

我在tomcat 8.0上使用java尝试了SSE(Server-Sent-Events)。以下是我注意到的一些事情。

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

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

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

  3. 确保关闭事件流并且服务器上不会产生资源开销的正确方法是什么?

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

  5. 在Tomcat上使用SSE为时尚早?任何性能问题?

  6. 以下是好奇的代码,

    @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

    记录了我所学到的知识

3 个答案:

答案 0 :(得分:21)

更改此行

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

writer.write("data: "+ i +"\r\n");
BTW,您的代码会出现严重的性能问题,因为它将保留一个线程,直到发送所有事件。请使用异步处理API。 e.g。

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会创建一个线程来处理每个请求。现在发生了什么?
  2.   

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

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

浏览器端的eventSource.close()是最佳选择。

  
      
  1. 确保关闭事件流并且服务器上不会产生资源开销的正确方法是什么?
  2.   

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

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

浏览器中的EventSource API仅支持GET请求,但在服务器端没有此类限制。 SSE主要用于从服务器接收事件数据。如果事件发生,浏览器可以及时接收它,并且不需要创建新的请求来轮询它。 如果需要全双工通信,请尝试使用SSS的WebSocket实例。

  
      
  1. 在Tomcat上使用SSE为时尚早?任何性能问题?
  2.   

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

答案 1 :(得分:1)

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

答案 2 :(得分:0)

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