龙空技术网

【技术分享】Web应用中数据推送技术的应用

重庆海云捷迅 156

前言:

而今我们对“java实现数据推送”都比较看重,朋友们都需要了解一些“java实现数据推送”的相关文章。那么小编在网络上汇集了一些有关“java实现数据推送””的相关内容,希望兄弟们能喜欢,兄弟们快快来了解一下吧!


在传统Web应用系统中大多数都是基于“请求/响应”模式。随着科学技术的发展和市场需求的变化,推送技术受到越来越多的关注。在C/S应用中推送技术已经很成熟,但它的缺点比较明显,每个客户端都需要安装相应的客户端软件来实现,这不利于软件的更新与维护。相比B/S模式下的推送应用具有跨平台、免客户端等优点。因此,很多C/S架构的软件逐渐向B/S架构转型。目前已被用到了很多重要领域,比如:实时通信工具、协同办公工等。


B/S架构下的推送原理


Web应用中采用的是HTTP协议,而HTTP协议是无状态的。也就是说,所有的数据交换都是基于请求/响应模式;当请求完成后,服务端和客户端将不再有任何联系。因此,若要实现数据推送,那就必须保持客户与服务端的数据交换通道。常用的技术方案包括:短连接、长连接、Server Sent、Webscoket等;接下来我们就将简单讲解一下几种技术的特点及区别。


AJAX短轮询

AJAX短轮询是server收到请求不管是否有数据到达都直接响应客户端请求;如果浏览器收到的数据为空,则等待定时任务再次执行,浏览器又会发送相同的http请求到server 以获取数据响应,如下图所示:


实现方式,这里我们实时获取服务时间为例:


【服务器端】提供获取系统时间的接口

@GetMapping("ajax/getTime")public Date getSystemTime() {  return new Date();}


【客户端】核心方法是setInterval每隔一秒去获取服务器时间

setInterval(getSystemTime, 1000)//定时轮询,短轮询核心代码function getSystemTime() {  $.get(";, function (result) {    var curTime = new Date(result).format("yyyy-MM-dd hh:mm:ss");    $("#message").text(curTime);  });}


▶优点:

实现起来简单,不需要过多额外实现

浏览器对短轮询兼容最高


▶缺点:

占用过多的带宽(一直不间断的请求服务网,头部请求占用数据太多)

消息不能及时传达(需要等待定时任务的执行)


长轮询

针对短轮询的缺点,那么应运而生了长轮询方式。服务器接收到客户端的请求后若存在更新数据则直接返回,若服务器不存在新数据则hold这个请求,直到存在更新的数据或者超时时才返回,如下图所示:


实现方式,我们还是以刚刚获取服务器时间为例:


【服务器端】

private final ExecutorService executorService = Executors.newCachedThreadPool();@GetMapping("/longPolling")public DeferredResult<Date> longPolling() {  DeferredResult<Date> result = new DeferredResult<>();  executorService.execute(() -> {    try {      Thread.sleep(2000);//系统无实现请求    } catch (InterruptedException e) {      log.error("系统错误", e);    }    result.setResult(new Date());  });  return result;}}


长轮询会hold请求,我们都知道容器线程池有限制的(tomcat默认200个,超过200个则进入等待队列,等待队列长度只有100,超过100后则拒绝链接),所以我们在拿到请求后直接把这个请求往自己的线程池丢,我们就不重复造轮子了,直接使用spring提供的DeferredResult。


【客户端】

function getSystemTime() {  $.get(";, function (result) {    var curTime = new Date(result).format("yyyy-MM-dd hh:mm:ss");    $("#message").text(curTime);    getSystemTime();//直接发送下次请求  });}


客户端改动比较小,只是第一次执行完后又继续调用。


执行结果:


相对短轮询,长轮询减少了带宽,消息相对更及时,不用像短轮询那样在前端定时间隔执行,还是存在以下问题未解决:

1、消息还是存在延时(主要是第一次与第二次还是需要重新握手建立链接)

2、HTTP头部信息占用过多带宽


SSE(Server-Sent Events)


http无法直接做到服务器端向客户端推送消息,但是我们在客户端可服务器创建链接后不直接断开,而是像下载文件一样使用流的形式一直向客户端发送数据。SSE就是使用了这种机制。



SSE有以下几个特点:

1、链接后默认文本流的形式传输,二进制数据需要转码

2、单通道传输,只能是服务器端向客户端传输,不能客户端向服务器端传输

3、每次响应都有固定的数据格式(data: some text )


【服务器端】

Executor execute = Executors.newSingleThreadExecutor();@GetMapping(value = "/sse/getTime", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public SseEmitter getSystemTime() {  SseEmitter emitter = new SseEmitter();  execute.execute(() -> {    try {      for (int i = 1000; i > 0; i--) {        emitter.send(new Date());        TimeUnit.SECONDS.sleep(1);      }      emitter.complete();    } catch (Exception e) {      emitter.completeWithError(e);    }  });  return emitter;}


在这里我们借助spring 提供的SseEmitter 类能快速实现自己的SSE,在原生编码中很多朋友会遇到没有生效,注意在响应头里面添加响应类型“text/event-stream”,若没有添加这个类型,这会当成普通的请求。


【客户端】

var evtSource = new EventSource(";, {withCredentials: true});evtSource.onmessage = function (event) {  $("#message").text(event.data);}evtSource.onopen = function () {  console.log('connection is established');};evtSource.onerror = function (event) {  console.log('connection state: ' + evtSource.readyState + ', error: ' + event);};


执行结果:


SSE时效性很高,实现起来也比较简单,不需要添加新组件,也不需要新的传输协议。可以使用任何服务器语言实现,但与此同时也存在一些问题:

1、SSE是文本协议(通常使用UTF-8编码)。当然,我们可以通过SSE连接传输二进制数据:在SSE中,只有两个具有特殊意义的字符,它们是CR和LF,而对它们进行转码并不难。但用SSE传输二进制数据时数据会变大。

2、SSE 是单通道,若想客户端向服务器端再次发送请求需要断开上次的连接,重新建立http链接,万幸的是SSE设计之初都支持重连技术。


Websocket

WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。

首先websocket 是一个全双工新传输协议,只是为了不重复造轮子复用了http创建链接,有效地解决了SSE的短板,支持客户端与服务器端双向通信,同时也支持二进制帧传输可以有效地传输图片、音频、视频等内容。传输过程如下图所示:



【服务器端】


1、添加依赖

<dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-websocket</artifactId></dependency>


2、添加配置服务

@Beanpublic ServerEndpointExporter serverEndpointExporter() {  return new ServerEndpointExporter();}


3、写一个websocketServer,用于存储客户端与服务器session ,特别注意这个session是websocket包下的session,不是servlet下的,在这个类中添加以下几个方法(基于Spring):


建立链接

@OnOpenpublic void open(Session session) {  this.session = session;  webSocketSet.add(this);     //加入set中  try {    sendMessage("建立链接成功");  } catch (IOException e) {    log.error("websocket IO Exception");  }}


发送消息

@OnMessagepublic void onMessage(String message, Session session) {//发送消息  for (WebsocketServer item : webSocketSet) {    try {     item.session.getBasicRemote().sendText(message);    } catch (IOException e) {      e.printStackTrace();    }  }}


关闭链接

@OnClosepublic void onClose() {  webSocketSet.remove(this); }


连接异常时

@OnErrorpublic void onError(Session session, Throwable error) {  error.printStackTrace();}


4、前端测试代码

var websocket = null;//判断当前浏览器是否支持WebSocketif('WebSocket' in window) {  //改成你的地址  websocket = new WebSocket("ws://127.0.0.1:8080/api/websocket/");} else {  console.log('当前浏览器不支持 websocket')}//连接发生错误的回调方法websocket.onerror = function() {  console.log("onerror");};websocket.onopen = function() {  console.log("onopen");}websocket.onclose = function() {  console.log("onclose");}//获取到消息websocket.onmessage = function(event) {  console.log(event);}//发送消息function send(message) {  websocket.send('{"msg":"' + message + '"}');}


执行结果


在上图中:

1. Connection字段告诉服务器需要切换协议,Upgrade字段告诉服务器需要切换成WebSocket协议。

2. Sec-WebSocket-Extensions表示客户端想要表达的协议级的扩展。

3. Sec-WebSocket-Key是一个Base64编码值,由浏览器随机生成,与响应头中Sec-WebSocket-Accept字段对应,表示了服务器与客户端能正常通信。

4. Sec-WebSocket-Version: 13表明客户端所使用的websocket版本。



SSE与websocket推送数据的情况怎么选呢?


首先在浏览器支持度方面差距不大,主流的浏览器都是支持的。一般会根据自己的业务场景进行判断:


1、我们推送的数据带有图片、音频、视频、文件等需要二进制传输的情况下选择websocket更佳。

2、根据互联网经验客户端向服务器端请求的频率是1秒每次,若频率高于1秒每次则选择websocket、若频率低于5秒每次则选择sse(频率太高,Ajax需要建立链接导致增加开销)


应用案例

海云捷迅旗下的人工智能工业缺陷检测系统中,我们采用了SSE推送方案实现金属表面缺陷检测过程中缺陷数据、任务状态数据的实时推送,从而加速前端UI渲染的速度,提升用户的使用体验。



总结

经过上面的讲解,您可能觉得短轮询这种落后的方式现实中不怎么使用了呢,恰恰相反,在互联网上,每个网民所使用的浏览器厂商、版本都不尽相同,为了满足所有网民都能正常使用功能,互联网的金主也不会计较流量以及那么点实时性。比如阿里、腾讯等的扫码登录他们都是使用了短轮询;那么在网页即时通讯、以及直播等领域我们就会看到大量使用了websocket技术,这类人群普遍能接受新版的浏览器,对新的技术接受度也更高;总之只有最适合的技术才是最好的技术。那么在我将上面的内容总结到下表,以便参考:


适用于哪些场景

目前在互联网应用中使用推送技术的领域很多,包括以下业务场景(但不限于):

在线人数统计(实时)

直播弹幕

实时监控平台

在线聊天

在线协同平台


股票系统


作者介绍

蒋祖兵,海云捷迅Java架构师,拥有丰富的项目经验,擅长分布式微服务架构设计。


标签: #java实现数据推送