服务器推送技术
服务器推送技术干嘛用?就是让用户在使用网络应用的时候,不需要一遍又一遍的去手动刷新就可以及时获得更新的信息。大家平时在上各种视频网站时,对视频节目进行欢乐的吐槽和评论,会看到各种弹幕,当然,他们是用flash技术实现的,对于我们没有用flash的应用,一样可以实现弹幕。又比如在股票网站,往往可以看到,各种股票信息的实时刷新,上面的这些都是基于服务器推送技术。
Ajax短轮询
Ajax短轮询就是用一个定时器不停的去网站上请求数据。
下面的代码实现浏览器页面实时显示服务器的当前时间。
服务器端的实现如下:
package com.morris.websocket.shortpoll;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.time.LocalDateTime;
@Controller
public class TimeController {
@RequestMapping("getTime")
@ResponseBody
public String getTime() {
return LocalDateTime.now().toString();
}
}
html页面实现如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Show Time</title>
</head>
<body>
当前时间为:<span id="timeSpan"></span>
<script type="text/javascript" src="/js/jquery-1.9.1.min.js"></script>
<script type="text/javascript">
function showTime(){
$.get("/getTime",function (data) {
console.log(data);
$("#timeSpan").html(data);
})
}
setInterval(showTime, 1000);
</script>
</form>
</body>
</html>
Comet
基于HTTP长连接、无须在浏览器端安装插件的“服务器推”技术为“Comet”,comet有下面两种实现方式:
- 基于AJAX的长轮询(long-polling)方式。
- 基于长连接的服务器推模型 Server-sent-events(SSE)。
基于AJAX的长轮询(long-polling)方式。
服务器端实现长轮询可以使用异步任务,这里使用的是Spring MVC对Servlet3.0规范所支持的异步请求方式。
下面的代码实现服务器端向浏览器页面实时推送新闻。
服务器端的实现如下:
package com.morris.websocket.longpoll;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
@Controller
public class PushNewsController {
@RequestMapping("realTimeNews")
@ResponseBody
public Callable<String> realtimeNews() {
Callable<String> callable = () -> {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
int index = new Random().nextInt(Const.NEWS.length);
return Const.NEWS[index];
};
return callable;
}
}
html页面实现如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>新闻推送</title>
</head>
<body>
<h1>每日头条</h1>
<div>
<div>
<h2>每日头条新闻实时看</h2>
<div style="color:#F00"><b><p id="realTimeNews"></p></b></div>
</div>
</div>
<script type="text/javascript" src="/js/jquery-1.9.1.min.js"></script>
<script type="text/javascript">
longLoop();
function longLoop() {
$.get("/realTimeNews", function (data) {
console.log(data);
$("#realTimeNews").html(data);
longLoop();
})
}
</script>
</body>
</html>
基于长连接的服务器推模型Server-sent-events(SSE)。
严格地说,HTTP协议无法做到服务器主动推送信息。但是,有一种变通方法,就是服务器向客户端声明,接下来要发送的是流信息(streaming)。也就是说,发送的不是一次性的数据包,而是一个数据流,会连续不断地发送过来。这时,客户端不会关闭连接,会一直等着服务器发过来的新的数据流,视频播放就是这样的例子。
本质上,这种通信就是以流信息的方式,完成一次用时很长的下载。
SSE就是利用这种机制,使用流信息向浏览器推送信息。它基于HTTP协议,目前除了IE/Edge,其他浏览器都支持。
下面的代码实现服务器端向浏览器页面实时推送贵金属最新价格。
服务器端的实现如下:
package com.morris.websocket.sse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.Random;
@Controller
public class NobleMetalController {
@RequestMapping("needPrice")
public void pushRight(HttpServletResponse response){
response.setContentType("text/event-stream");
response.setCharacterEncoding("utf-8");
Random r = new Random();
try {
PrintWriter pw = response.getWriter();
int i = 0;
while(i<10){
if(pw.checkError()){
System.out.println("客户端断开连接");
return;
}
Thread.sleep(1000);
pw.write(makeResp(r));
pw.flush();
i++;
}
System.out.println("达到阈值,结束发送.......");
pw.write("data:stop\n\n");
pw.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
/*业务方法,生成贵金属的实时价格*/
private String makeResp(Random r){
StringBuilder stringBuilder = new StringBuilder("");
stringBuilder.append("retry:2000\n")
.append("data:")
.append(r.nextInt(100)+50+",")
.append(r.nextInt(40)+35)
.append("\n\n");
return stringBuilder.toString();
}
}
html页面实现如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>贵金属</title>
</head>
<body>
<h1>贵金属</h1>
<div>
<div>
<h2>贵金属列表</h2>
</div>
<div>
<h2 id="hint"></h2>
</div>
<hr>
<div>
<div><p>黄金</p>
<p id="c0" style="color:#F00"></p><b><p id="s0">历史价格:</p></b></div>
<div><p>白银</p>
<p id="c1" style="color:#F00"></p><b><p id="s1">历史价格:</p></b></div>
</div>
<hr>
</div>
<script type="text/javascript" src="/js/jquery-1.9.1.min.js"></script>
<script type="text/javascript">
function showPrice(index, data) {
$("#c" + index).html("当前价格:" + data);
var s = $("#s" + index).html();
$("#s" + index).html(s + data + " ");
}
if (!!window.EventSource) {
var source = new EventSource('/needPrice');
source.onmessage = function (e) {
var dataObj = e.data;
var arr = dataObj.split(',');
$.each(arr, function (i, item) {
showPrice(i, item);
});
$("#hint").html("");
};
source.onopen = function (e) {
console.log("Connecting server!");
};
source.onerror = function () {
console.log("error");
};
} else {
$("#hint").html("您的浏览器不支持SSE!");
}
</script>
</body>
</html>
各种服务器推送技术的比较
短轮询 | 长轮询 | SSE | WebSocket |
浏览器支持度 | 最高 | 很高 | 中(IE和Edge均不支持) |
实时性 | 最低 | 较高 | 很高 |
代码实现复杂度 | 最容易 | 较容易 | 容易 |
连接性质 | 短连接 | 长连接 | 长连接 |
适用 | 需要服务极大量或极小量的用户,实时性要求不高 | 准实时性的应用,比较关注浏览器的兼容性 | 实时,基本都是文本交互的应用 |
应用场景
服务器推送技术常用于二维码登录,二维码支付等场景。
淘宝登录用的什么?Ajax短轮询,这说明什么?这些技术并没有什么优劣之分,只有合不合适业务的问题。淘宝的痛点是什么?要用有限的资源来为千万级甚至上亿的用户提供服务,如果是用长连接,对于接入的服务器,比如说 Nginx,是很大的压力,光是为用户维持这个长连接都需要成百上千的Nginx的服务器,这是很划不来的。