最近接触到了SSE,先说一下概念:
SSE ( Server-sent Events )是 WebSocket 的一种轻量代替方案,使用 HTTP 协议。
不同于WebSocket,SSE 是单向通道,只能服务器向客户端发送消息。但对于一般应用来说,客户端向服务器发消息可以直接GET/POST,因此并不是很迫切;而服务器下发才是有一定应用场景的,可以避免客户端频繁的轮询服务器。
SSE 最大的优势是简单,对服务器端和客户端依赖都很少:
服务端:无额外的引用,只需要实现一个Servlet就可以(Servlet 3.0),10几行代码
客户端:只要浏览器支持EventSource,4、5行代码就可以实现。目前主流浏览器基本都支持:

首先实现服务器端,一个Handler,URL为/sse/demo,其中关键的代码是
request.startAsync(),启动一个异步请求,获得了AsyncContext异步请求的上下文
使用AsyncContext.getResponse()获得输出流向客户端发送数据
数据格式是用回车换行符(\r\n)分隔的多行字符串:
id:消息ID\r\n
event:消息类型\r\n
data:数据\r\n
public class SSEHandler extends Handler {
private final static int DEFAULT_TIME_OUT = 30 * 1000;
@Override
public void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) {
if (target.endsWith("/sse/demo")) {
response.setContentType("text/event-stream");
response.setCharacterEncoding("UTF-8");
request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);
AsyncContext actx = request.startAsync(request, response);
actx.setTimeout(DEFAULT_TIME_OUT);
actx.addListener(new AsyncListener() {
@Override
public void onComplete(AsyncEvent arg0) throws IOException {
// TODO Auto-generated method stub
System.out.println("[echo]event complete:" + ((HttpServletRequest)arg0.getSuppliedRequest()).getSession().getId());
}
@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("[echo]event time lost");
}
});
new Thread(new AsyncWebService(actx)).start();
isHandled[0] = true;
}
else {
if (next != null) {
next.handle(target, request, response, isHandled);
}
}
}
}
class AsyncWebService implements Runnable {
AsyncContext ctx;
public AsyncWebService(AsyncContext ctx) {
this.ctx = ctx;
}
public void run() {
try {
PrintWriter out = ctx.getResponse().getWriter();
out.println("event:demo\r\nid:101\r\ndata:中文" + new Date() + "\r\n"); //js页面EventSource接收数据格式:id:[消息Id]\r\nevent:[事件类型]\r\ndata:[数据]\r\n"
out.flush();
ctx.complete();
//等待十秒钟,以模拟业务方法的执行
Thread.sleep(10000);
}
catch (Exception e) {
e.printStackTrace();
}
}
}客户端代码:
new EventSource('/sse/demo'),连接到服务器对应的URL
source.addEventListener('demo', function(e) {...}, false),添加监听器
<html>
<head>
<meta charset="utf-8" />
<script type="text/javascript">
//jsp页面js脚本
if (!!window.EventSource) { //EventSource是SSE的客户端.此时说明浏览器支持EventSource对象
var source = new EventSource('/sse/demo');//发送消息
s = '';
source.addEventListener('demo', function(e) {
console.info(e);
s += e.data + "<br/>";
document.querySelector("#msgFromPush").innerHTML = (s);
}, false);//添加客户端的监听
source.addEventListener('open', function(e) {
console.log("连接打开");
}, false);
source.addEventListener('error', function(e) {
if (e.currentTarget.readyState == EventSource.CLOSED) {
console.log("连接关闭");
} else {
console.log(e.currentTarget.readyState);
}
});
} else {
console.log("您的浏览器不支持SSE");
}
</script>
</head>
<body>
<div id="msgFromPush" />
</body>
</html>以上,完成。
看看效果:
客户端:

服务器端,现在打印的是SessionId,每次调用都不同是因为客户端未登录,如果客户端登录了,SessionId就固定了:

是不是够简单?以后可以做为备选方案了。