服务器发送事件是如何工作的



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

我单击一个按钮,该按钮自动向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 +"nn");
            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,所以这一定是一个问题与chrome ??

编辑:

我学到的和学到的东西现在都记录在我的博客-服务器发送事件

改变这一行

writer.write("data: "+ i +"nn");

writer.write("data: "+ i +"rn");

顺便说一句,你的代码将有一个严重的性能问题,因为它将保持一个线程,直到所有的事件被发送。请使用异步处理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 + "rn");
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: 36000000000rn"); // 10000 hours!
    actx.getResponse().getWriter().flush();
    //save actx and use it when we need sent data to the client.
}
更新2:

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

UPDATE 3:(回答问题)

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

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

什么是正确的方式来确保事件流只发送一次到相同的连接/浏览器会话?

在浏览器端执行eventSource.close()是最佳选择。

  • 确保事件流关闭且服务器上没有资源开销的正确方法是什么?
  • 不要忘记在服务器端调用AsyncContext.complete()。

  • 如何区分GET和POST请求。为什么选择GET?
  • 浏览器中的EventSource API只支持GET请求,但在服务器端没有这样的限制。SSE主要用于接收来自服务器的事件数据。如果一个事件发生,浏览器可以及时接收它,而不需要创建一个新的请求来轮询它。如果您需要全双工通信,请尝试使用WebSocket代替SSE。

  • 在Tomcat上使用SSE是否为时过早?有性能问题吗?
  • 如果我们使用NIO连接器,应该没有性能问题&异步处理API。我不知道Tomcat NIO连接器是否成熟,但有些东西除非我们尝试,否则永远不会知道。

    我强烈建议首先阅读使用服务器发送事件的流更新,以获得对该技术的良好总体理解。然后按照使用异步Servlet的服务器发送事件示例来了解SSE是如何与Servlet技术一起使用的。

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

    相关内容

    • 没有找到相关文章

    最新更新