1.为什么用SSE?
最近对原项目进行重构,在计数统计这块,原来项目用的是websocket,这个太复杂了,没有必要,就使用了SSE。
2.和其他网上文章的不同之处在于:
因为是用在项目,用区域对点对点进行了匹配,实际一点。用的是AsyncContext。
3.参考链接:
阮一峰的Server-Sent Events 教程:http://www.ruanyifeng.com/blog/2017/05/server-sent_events.html
一般实现:https://blog.youkuaiyun.com/ocean_fan/article/details/79225203
关于长连接、跳出循环:https://www.jianshu.com/p/100b82730e15
点对点原理:https://www.jianshu.com/p/573241b4dbd2
更多AsyncContext细节:https://book.2cto.com/201301/14823.html
4.代码
实现的原理:客户端直接在页面发起请求,在服务端接收后,存储到AsyncContext的list,在实际产生要推动的数据后,调用接口,判断标识(区域id,你也可以定用户id等)进行AsyncContext发送。
页面
<!DOCTYPE html>
<html>
<head>
<title>sse.html</title>
<meta name="keywords" content="keyword1,keyword2,keyword3">
<meta name="description" content="this is my page">
<meta name="content-type" content="text/html; charset=UTF-8">
<!--<link rel="stylesheet" type="text/css" href="./styles.css">-->
</head>
<body>
This is my HTML page. <br>
<input type="button" id="connect" value="Connect" /><br />
<span id="status">Connection closed!</span><br />
<span id="connecttime"></span><br />
<span id="output"></span>
<div id="msg_from_server">12222</div>
</body>
<script type="text/javascript">
if (!!window.EventSource) {
var output = document.getElementById("output");
var source = new EventSource('http://localhost:8080/api/sse/push?areaId=2'); //为http://localhost:8080/testSpringMVC/push
s = '';
source.addEventListener('message', function(e) {
s += e.data + "<br/>";
document.getElementById("output").innerText=s;
});
source.addEventListener('open', function(e) {
output.textContent = "连接打开.";
}, false);
source.addEventListener('error', function(e) {
if (e.readyState == EventSource.CLOSED) {
output.textContent = "连接关闭";
} else {
console.log(e.readyState);
}
}, false);
} else {
alert(4);
console.log("没有sse");
}
</script>
</html>
controller
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.AsyncContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* @author: sw17028
* @date:2018年12月20日 上午11:02:29
* @version :
*
*/
@Controller
//@WebServlet(urlPatterns = {"/api/sse" }, asyncSupported = true)
@RequestMapping(value="/api/sse")
public class SSEController {
public static List<AsyncContext> actxList =new ArrayList<AsyncContext>();
//存储区域和通道信息
public static Map<HttpSession, AsyncContext> sseMap = new HashMap<HttpSession,AsyncContext>();
@RequestMapping(value = "/push", produces = "text/event-stream"
// + ";charset=UTF-8"
+ "")
public @ResponseBody void push(HttpServletRequest request, HttpServletResponse response) {
response.setContentType("text/event-stream");
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Connection", "keep-alive");
response.setCharacterEncoding("UTF-8");
request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);
AsyncContext actx = request.startAsync(request,response);
actx.setTimeout(600000);
actx.addListener(new AsyncListener() {
@Override
public void onComplete(AsyncEvent asyncEvent) throws IOException {
// TODO Auto-generated method stub
// System.out.println("zxczxc");
}
@Override
public void onError(AsyncEvent arg0) throws IOException {
// TODO Auto-generated method stub
// System.out.println("[echo]event has error");
}
@Override
public void onStartAsync(AsyncEvent arg0) throws IOException {
// TODO Auto-generated method stub
// System.out.println("[echo]event start:" + arg0.getSuppliedRequest().getRemoteAddr());
}
@Override
public void onTimeout(AsyncEvent arg0) throws IOException {
// TODO Auto-generated method stub
// System.out.println(arg0);
}
});
actxList.add(actx);
}
}
代码中保留了一些无用的注释,可以给大家做一些和其他文章的参考对照,不需要的可以直接删除。
调用接口:public void sendMessageByArea(Integer areaId, String message);
@Override
public void sendMessageByArea(Integer areaId, String message) {
// TODO Auto-generated method stub
for(AsyncContext asyncContext : SSEController.actxList){
//如果文中包含areaid,则发送
if(asyncContext.getTimeout()<0){
SSEController.actxList.remove(asyncContext);
System.out.println("客户端断开连接");
}
else if(areaId.toString().equals(
asyncContext.getRequest().getParameter("areaId"))
){
try {
Thread.sleep(1000);
PrintWriter pw = asyncContext.getResponse().getWriter();
if(pw.checkError()){
SSEController.actxList.remove(asyncContext);
System.out.println("客户端断开连接");
return;
}
pw.write("data:"+message + "\n\n");
pw.flush();
}
catch (InterruptedException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
return;
}
}
}
}