Server-Sent-Events如何工作

我在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 +"\n\n"); writer.flush(); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } writer.close(); } } 

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

   function start() { var eventSource = new EventSource("TestServlet"); eventSource.onmessage = function(event) { console.log("data: "+event.data) document.getElementById('foo').innerHTML = event.data; }; }  

使用CURL尝试了这个。 响应只发了一次。 我正在使用chrome,所以这一定是chorme的一个问题?

编辑:

现在我的博客 – 服务器发送事件记录了我学到的知识和学习内容

改变这一行

 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主要用于从服务器接收事件数据。 如果事件发生,浏览器可以及时接收它,并且不需要创建新的轮询请求。 如果需要全双工通信,请尝试使用SSS的WebSocket实例。

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

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

我强烈建议首先阅读使用服务器发送事件的流更新,以便对该技术有一个很好的了解。 然后按照服务器发送事件使用Async Servlet按示例查看SSE如何专门用于Servlet技术。