Java 服务器发送事件如何工作
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/31768349/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How does Server-Sent-Events work
提问by John
I tried an SSE (Server-Sent-Events) using java on tomcat 8.0. Here are few things I noticed.
我在 tomcat 8.0 上使用 java 尝试了 SSE(服务器发送事件)。以下是我注意到的几件事。
I click a button that automatically makes a request to the servlet. Servlet's GET method gets executed which returns an event stream. Once the full stream is received, the page again automatically makes another request which receives the same data again!!! I don't have an infinite loop there!!!
我单击了一个自动向 servlet 发出请求的按钮。Servlet 的 GET 方法被执行,它返回一个事件流。一旦接收到完整的流,页面再次自动发出另一个再次接收相同数据的请求!!!我那里没有无限循环!!!
What is actually happening on the server? In normal scenarios, tomcat creates a thread to handle every request. What is happening now?
What is the correct way to ensure that the event stream is sent only once to the same connection/browser session?
What is the correct way to ensure that the event stream is closed and no resource overhead incurs on the server?
How to differentiate between GET and POST requests. Why did it choose GET?
Is it too early to use SSE on Tomcat? Any performance issues?
服务器上实际发生了什么?在正常场景下,tomcat 会创建一个线程来处理每个请求。现在发生了什么?
确保事件流仅向同一连接/浏览器会话发送一次的正确方法是什么?
确保事件流关闭并且服务器上不会产生资源开销的正确方法是什么?
如何区分 GET 和 POST 请求。为什么选择GET?
在 Tomcat 上使用 SSE 是否为时过早?任何性能问题?
Here is the code for the curious,
这是好奇的代码,
@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 on the page (I don't have anything else on the page),
页面上的 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>
Tried this using CURL. And the response came just once. I'm using chrome, so this must be a issue with chorme??
使用 CURL 尝试了这个。而回应只来了一次。我正在使用 chrome,所以这一定是 chorme 的问题??
EDIT:
编辑:
What I have learned and learning is now documented in my blog - Server Sent Events
我所学到的和学习的东西现在记录在我的博客中 -服务器发送的事件
采纳答案by xfeep
Change this line
改变这一行
writer.write("data: "+ i +"\n\n");
to
到
writer.write("data: "+ i +"\r\n");
BTW, your code will have a serious performance issue because it will hold a thread until all events are sent.Please use Asynchronous processing API instead. e.g.
顺便说一句,您的代码将有一个严重的性能问题,因为它会一直持有一个线程,直到所有事件都发送完毕。请改用异步处理 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.
}
Then we can use AsyncContext later
然后我们可以稍后使用 AsyncContext
//write some data to client when a certain event happens
actx.getResponse().getWriter().write("data: " + mydata + "\r\n");
actx.getResponse().getWriter().flush();
if all events sent we can close it
如果所有事件都发送了,我们可以关闭它
actx.complete();
UPDATE 1:
更新1:
We need close the event source at browser if we do not want browser reconnect the server again when server completes the response.
如果我们不希望浏览器在服务器完成响应时再次重新连接服务器,我们需要关闭浏览器的事件源。
eventSource.close();
Another method maybe helps, viz. we set a quite large retry time but I have not tried it, e.g.
另一种方法可能有帮助,即。我们设置了相当长的重试时间,但我还没有尝试过,例如
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.
}
UPDATE 2:
更新 2:
I think Websocket maybe is better for your case.
我认为 Websocket 可能更适合您的情况。
UPDATE 3: (answer the questions)
更新3:(回答问题)
- What is actually happening on the server? In normal scenarios, tomcat creates a thread to handle every request. What is happening now?
- 服务器上实际发生了什么?在正常场景下,tomcat 会创建一个线程来处理每个请求。现在发生了什么?
If use NIO connector which is default in Tomcat 8.0.X, within the whole processing cycle HTTP I/O about a request won't hold a thread. If use BIO a thread will be hold until the whole processing cycle completes. All threads are from a thread pool, tomcat won't create a thread for each request.
如果使用 Tomcat 8.0.X 中默认的 NIO 连接器,在整个处理周期内,关于请求的 HTTP I/O 不会持有线程。如果使用 BIO,一个线程将被保留,直到整个处理周期完成。所有线程都来自一个线程池,tomcat 不会为每个请求创建一个线程。
- What is the correct way to ensure that the event stream is sent only once to the same connection/browser session?
- 确保事件流仅向同一连接/浏览器会话发送一次的正确方法是什么?
Do eventSource.close()
at browser side is the best choice.
不要eventSource.close()
在浏览器端是最好的选择。
- What is the correct way to ensure that the event stream is closed and no resource overhead incurs on the server?
- 确保事件流关闭并且服务器上不会产生资源开销的正确方法是什么?
Do not forget to invoke AsyncContext.complete() at server side.
不要忘记在服务器端调用 AsyncContext.complete() 。
- How to differentiate between GET and POST requests. Why did it choose GET?
- 如何区分 GET 和 POST 请求。为什么选择GET?
The EventSource API in a browser only supports GET requests but at the server side there 's no such restriction. SSE is mainly used to receive events data from server. If a event happens the browser can receive it in time and no need to create a new request to poll it. If you need full-duplex communication try WebSocket instread of SSE.
浏览器中的 EventSource API 只支持 GET 请求,但在服务器端没有这样的限制。SSE 主要用于从服务器接收事件数据。如果事件发生,浏览器可以及时接收到它,而无需创建新的请求来轮询它。如果您需要全双工通信,请尝试使用 SSE 的 WebSocket instread。
- Is it too early to use SSE on Tomcat? Any performance issues?
- 在 Tomcat 上使用 SSE 是否为时过早?任何性能问题?
There should be no performance issues if we use NIO connector & Asynchronous processing API. I don't know whether Tomcat NIO connector is mature or not but something will never be known unless we try it.
如果我们使用 NIO 连接器和异步处理 API,应该不会有性能问题。我不知道 Tomcat NIO 连接器是否成熟,但除非我们尝试,否则永远不会知道某些东西。
回答by 01es
I highly recommend first to read Stream Updates with Server-Sent Eventsto get a good general understanding of the technology. Then follow Server-Sent Events with Async Servlet By Exampleto see how SSE can be used specifically with the Servlet technology.
我强烈建议首先阅读带有服务器发送事件的流更新,以对该技术有一个很好的总体了解。然后按照服务器发送事件和 Async Servlet By Example来看看 SSE 是如何专门用于 Servlet 技术的。