Comet 有时也称反向 Ajax 或服务器端推技术(server-side push)。其思想很简单:将数据直接从服务器推到浏览器,而不必等到浏览器请求数据。
Ajax 的出现使 Comet 成为可能。HTTP 的单向性质可以有效地加以规避。实际上有一些不同的方法可以绕过这一点。您可能已经猜到,支持 Comet 的最容易的方式是轮询(poll)。使用XMLHttpRequest
向服务器发出调用,返回后,等待一段固定的时间(通常使用 JavaScript 的setTimeout
函数),然后再次调用。这是一项非常常见的技术。例如,大多数 webmail 应用程序就是通过这种技术在电子邮件到达时显示电子邮件的。
这项技术有优点也有缺点。在这种情况下,您期望快速返回响应,就像任何其他 Ajax 请求一样。在请求之间必须有一段暂停。否则,连续不断的请求会冲垮服务器,并且这种情况下显然不具有可伸缩性。这段暂停使应用程序产生一个延时。暂停的时间越长,服务器上的新数据就需要越多的时间才能到达客户机。如果缩短暂停时间,又将重新面临冲垮服务器的风险。但是另一方面,这显然是最简单的实现 Comet 的方式。
现在应该指出,很多人认为轮询并不属于 Comet。相反,他们认为 Comet 是对轮询的局限性的一个解决方案。最常见的 “真正的” Comet 技术是轮询的一种变体,即长轮询(long polling)。轮询与长轮询之间的主要区别在于服务器花多长的时间作出响应。长轮询通常将连接保持一段较长的时间 — 通常是数秒钟,但是也可能是一分钟甚至更长。当服务器上发生某个事件时,响应被发送并随即关闭,轮询立即重新开始。
长轮询相对于一般轮询的优点在于,数据一旦可用,便立即从服务器发送到客户机。请求可能等待较长的时间,期间没有任何数据返回,但是一旦有了新的数据,它将立即被发送到客户机。因此没有延时。如果您使用过基于 Web 的聊天程序,或者声称 “实时” 的任何程序,那么它很可能就是使用了这种技术。
长轮询有一种变体,这是第三种风格的 Comet。这通常被称为流(streaming)。按照这种风格,服务器将数据推回客户机,但是不关闭连接。连接将一直保持开启,直到过期,并导致重新发出请求。XMLHttpRequest
规范表明,可以检查readyState
的值是否为 3 或 Receiving(而不是 4 或 Loaded),并获取正从服务器 “流出” 的数据。和长轮询一样,这种方式也没有延时。当服务器上的数据就绪时,该数据被发送到客户机。这种方式的另一个优点是可以大大减少发送到服务器的请求,从而避免了与设置服务器连接相关的开销和延时。不幸的是,XMLHttpRequest
在不同的浏览器中有很多不同的实现。这项技术只能在较新版本的 Mozilla Firefox 中可靠地使用。对于 Internet Explorer 或 Safari,仍需使用长轮询。
至此,您可能会想,长轮询和流都有一个很大的问题。请求需要在服务器上存在一段较长的时间。这打破了每个请求使用一个线程的模型,因为用于一个请求的线程一直没有被释放。更糟糕的是,除非要发回数据,否则该线程一直处于空闲状态。这显然不具有可伸缩性。幸运的是,现代 Web 服务器有很多方式可以解决这个问题。
我们要了解反向Ajax、轮询(polling)、流(streaming)、Comet和长轮询(long polling),学习如何实现不同的反向Ajax通信技术,并探讨每种方法的优点和缺点。
Ajax、反向Ajax和WebSocket
异步的JavaScript和XML(Asynchronous JavaScript and XML,Ajax),一种可通过JavaScript来访问的浏览器功能特性,其允许脚本向幕后的网站发送一个HTTP请求而又无需重新加载页面。Ajax的出现已经超过了十年,尽管其名字中包含了XML,但你几乎可以在Ajax请求中传送任何的东西,最常用的数据是JSON,其与JavaScript语法很接近,且消耗更少带宽。清单1给出了这样的一个例子,Ajax请求通过某个地方的邮政编码来检索该地的名称。
清单1. Ajax请求举例
+ $( ' #postalCode ' ).val() + ' &country= '
+ $( ' #country ' ).val() + ' &callback=? ' ;
$.getJSON(url, function (data) {
$( ' #placeName ' ).val(data.postalcodes[ 0 ].placeName);
});
在本文可下载的源代码中,你可在listing1.html中看到这一例子的作用。
反向Ajax(Reverse Ajax)本质上则是这样的一种概念:能够从服务器端向客户端发送数据。在一个标准的HTTP Ajax请求中,数据是发送给服务器端的,反向Ajax可以某些特定的方式来模拟发出一个Ajax请求,这些方式本文都会论及,这样的话,服务器就可以尽可能快地向客户端发送事件(低延迟通信)。
WebSocket技术来自HTML5,是一种最近才出现的技术,许多浏览器已经支持它(Firefox、Google Chrome、Safari等等)。WebSocket启用双向的、全双工的通信信道,其通过某种被称为WebSocket握手的HTTP请求来打开连接,并用到了一些特殊的报头。连接保持在活动状态,你可以用JavaScript来写和接收数据,就像是正在用一个原始的TCP套接口一样。WebSocket会在这一文章系列的第二部分中谈及。
反向Ajax技术
反向Ajax的目的是允许服务器端向客户端推送信息。Ajax请求在缺省情况下是无状态的,且只能从客户端向服务器端发出请求。你可以通过使用技术模拟服务器端和客户端之间的响应式通信来绕过这一限制。
HTTP轮询和JSONP轮询
轮询(polling)涉及了从客户端向服务器端发出请求以获取一些数据,这显然就是一个纯粹的Ajax HTTP请求。为了尽快地获得服务器端事件,轮询的间隔(两次请求相隔的时间)必须尽可能地小。但有这样的一个缺点存在:如果间隔减小的话,客户端浏览器就会发出更多的请求,这些请求中的许多都不会返回任何有用的数据,而这将会白白地浪费掉带宽和处理资源。
图1中的时间线说明了客户端发出了某些轮询请求,但没有信息返回这种情况,客户端必须要等到下一个轮询来获取两个服务器端接收到的事件。
图1. 使用HTTP轮询的反向Ajax
JSONP轮询基本上与HTTP轮询一样,不同之处则是JSONP可以发出跨域请求(不是在你的域内的请求)。清单1使用JSONP来通过邮政编码获取地名,JSONP请求通常可通过它的回调参数和返回内容识别出来,这些内容是可执行的JavaScript代码。
要在JavaScript中实现轮询的话,你可以使用setInterval来定期地发出Ajax请求,如清单2所示:
清单2. JavaScript轮询
$.getJSON( ' events ' ,255)">function (events) {
console.log(events);
});
}, 2000 );
文章源代码中的轮询演示给出了轮询方法所消耗的带宽,间隔很小,但可以看到有些请求并未返回事件,清单3给出了这一轮询示例的输出。
清单3. 轮询演示例子的输出
] no event
] 2 events
event ] At Sun Jun 05 15 : 17 : 14 EDT 2011
] 1 events
] At Sun Jun 05 15 : 17 : 16 EDT 2011
用JavaScript实现的轮询的优点和缺点:
1. 优点:很容易实现,不需要任何服务器端的特定功能,且在所有的浏览器上都能工作。
2. 缺点:这种方法很少被用到,因为它是完全不具伸缩性的。试想一下,在100个客户端每个都发出2秒钟的轮询请求的情况下,所损失的带宽和资源数量,在这种情况下30%的请求没有返回数据。
Piggyback
捎带轮询(piggyback polling)是一种比轮询更加聪明的做法,因为它会删除掉所有非必需的请求(没有返回数据的那些)。不存在时间间隔,客户端在需要的时候向服务器端发送请求。不同之处在于响应的那部分上,响应被分成两个部分:对请求数据的响应和对服务器事件的响应,如果任何一部分有发生的话。图2给出了一个例子。
图2. 使用了piggyback轮询的反向Ajax
在实现piggyback技术时,通常针对服务器端的所有Ajax请求可能会返回一个混合的响应,文章的下载中有一个实现示例,如下面的清单4所示。
清单4. piggyback代码示例
$.post( ' ajax ' , function (data) {
var valid = data.formValid;
// 处理验证结果
// 然后处理响应的其他部分(事件)
processEvents(data.events);
});
});