我在tomcat 8.0上使用java尝试了SSE(服务器发送事件)。这是我注意到的几件事。
我单击一个按钮,该按钮会自动向Servlet发出请求。执行Servlet的GET方法,该方法返回事件流。一旦接收到完整的流,页面将再次自动发出另一个请求,该请求再次接收相同的数据!!!我在那里没有无限循环!!!
服务器上实际发生了什么?在正常情况下,tomcat创建一个线程来处理每个请求。现在发生了什么?
确保事件流仅发送到同一连接/浏览器会话一次的正确方法是什么?
确保事件流关闭且服务器上没有资源开销的正确方法是什么?
如何区分GET和POST请求。为什么选择GET?
在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一定是一个问题?
编辑:
我所学到的东西现在记录在我的博客中- 服务器已发送事件
更改此行
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 :(回答问题)
如果使用Tomcat 8.0.X中默认的NIO连接器,则在整个处理周期内,有关请求的HTTP I / O将不会容纳线程。如果使用BIO,则将保留一个线程,直到整个处理周期结束。所有线程均来自线程池,tomcat不会为每个请求创建线程。
不要eventSource.close()在浏览器端是最好的选择。
eventSource.close()
不要忘记在服务器端调用AsyncContext.complete()。
浏览器中的EventSource API仅支持GET请求,但在服务器端没有这种限制。SSE主要用于从服务器接收事件数据。如果发生事件,浏览器可以及时接收到它,而无需创建新的请求以对其进行轮询。如果需要全双工通信,请尝试使用SSE的WebSocket。
如果我们使用NIO连接器和异步处理API,应该不会有性能问题。我不知道Tomcat NIO连接器是否成熟,但是除非我们尝试尝试,否则永远不会知道。