当需要前端频繁的请求后端数据的时候,比如说数据的实时显示,这种情况下产生问题的核心原因是:服务器知道数据什么时候更新,但浏览器不知道。
这种情况下,即使对于强大的Ajax,也是一个难以解决的问题。
解决方案1:频繁轮询
当我们手中只有ajax工具的时候,我们往往会选择这样的解决方式:
通过设定定时器,以一个固定的频率(比如1秒/次)将ajax请求打到服务器查询新的数据。如果后端有数据,则返回新数据,如果没有,就返回空。
这样的协议在请求不需要那么频繁并且用户量小的时候,勉强能用。很明显,该协议有大量的请求被浪费了,并且服务器做出了大量无效的响应。
解决方案2:长轮询
长轮询其实是频繁轮询的一个妥协,他与频繁轮询一样,也是需要前端不断的打请求,但是如果服务器没有新数据,那么服务器对前端的请求不做响应。
该方法会出现下面三个问题:
- 如果浏览器在服务器响应之前,有新数据发送到服务器怎么办?
浏览器必须创建一个新的并行请求,或者终止当前请求然后创建新的请求。 - TCP和HTTP连接都指定了连接超时,所以服务器和客户端必须周期性的关闭和重建连接。
- HTTP/1.1规范中存在着强制的连接限制。浏览器最多只允许同时创建两个到相同主机名的连接。如果一个连接长期连接到服务器等待数据推送,那么它将减少一般可用于从服务器抓去web页面、图形和其他资源的连接。
在过去几年,长轮询得到了广泛的应用,通常被称为Comet。
解决方案3: 分块编码
持续连接的问题:对于非持续连接,浏览器可以通过连接是否关闭来界定请求或响应实体的边界;而对于持续连接,这种方法显然不奏效。有时,尽管我已经发送完所有数据,但浏览器并不知道这一点,它无法得知这个打开的连接上是否还会有新数据进来,只能傻傻地等了。
为了不让浏览器等待,响应对象一般使用一个Content-Length:n头来告知浏览器这个数据有多长。浏览器可以通过 Content-Length 的长度信息,判断出响应实体已结束。
Content-length引入的新问题:由于 Content-Length 字段必须真实反映实体长度,但是对于动态生成的内容来说,在内容创建完之前,长度是不可知的。这时候要想准确获取长度,只能开一个足够大的 buffer,等内容全部生成好再计算。但这样做一方面需要更大的内存开销,另一方面也会让客户端等更久。
我们需要一个新的机制:不依赖头部的长度信息,也能知道实体的边界——分块编码(Transfer-Encoding: chunked)。
分块编码非常类似于长轮询,它利用了HTTP/1.1的特性:服务器可以在不声明内容长度的情况下响应请求。
响应在不使用Content-Length:n头时,可以使用Transfer-Encoding:chunked头。这样将告诉浏览器 响应对象将被“分块发送”。
利用这一特性,在开始的时候创建一个连接,只用于接受服务器发送的时间,来自服务器的每一个块都是一个新事件,它们将出发JavaScript XMLHttpRequest对象的onreadystatechange时间处理器的调用。
尽管不如长轮询那么频繁,但是连接仍然需要更新。
解决方案4: Applet和Adobe Flash
在演变的过程中,人们渐渐意识到这些解决方法所做的其实是通过单个连接来模拟全双工通信。简单来说,Ajax和XMLHttpRequest无法完成该任务,一个更流行尽管更短命的解决方案是Java Applet或者Adobe Flash。
开发者将创建一个含有1个像素的普通透明Applet或者Flash影片,并内嵌在网页中,然后这个插件将创建出连接到服务器的TCP Socket连接(而不是Http连接)。这种方式消除了HTTP协议中的制约限制。
该协议在单个连接上实现了真正的全双工通信,并消除了例如超时和兵法连接限制这样的问题,但也带来了很高的代价:使用第三方插件。
很显然,这两种技术在今天已经被淘汰了。
WebSocket
HTTP升级特性包含了如下一点:
最终我们可以使用任意的协议。
这意味着,在HTTP升级特性 在握手完成后,就可以不再使用HTTP连接,而是使用一个持久的、全双工的TCP Socket连接。
理论上讲这是行得通的,但是浏览器不会让JavaScript开发者随意使用TCP栈,所以就需要指定某些协议,因此就产生了WebSocket协议。
WebSocket连接首先将使用非正常的HTTP请求以特定的模式访问一个URL。URL模式ws和wss分别对应于HTTP的http和https。通过一个Connection:websocket头告诉服务器把连接升级称为WebScoket协议——一个全双工持久的通信协议。
WebScoket协议的实现方式有许多优点:
- 因为连接在端口80(ws)或者443(wss)上创建,与HTTP使用的端口相同,几乎所有的防火墙都不会阻塞WebSocket连接。
- 因为它使用HTTP握手,该协议可以集成到网络浏览器和HTTP服务器中。
- 心跳机制。
- WebSocket连接关闭时将发送一个特殊的关闭消息,其中可以包含原因代码和用于解释连接被关闭原因的文本。