优就业Ajax教程-反向Ajax,第1部分:Comet介绍英文原文:ReverseAjax,Part1:IntroductiontoComet在过去的几年中,web开发已经发生了很大的变化。现如今,我们期望的是能够通过web快速、动态地访问应用。在这一新的文章系列中,我们学习如何使用反向Ajax(ReverseAjax)技术来开发事件驱动的web应用,以此来实现更好的用户体验。客户端的例子使用的是JQueryJavaScript库,在这首篇文章中,我们探索不同的反向Ajax技术,使用可下载的例子来学习使用了流(streaming)方法和长轮询(longpolling)方法的Comet。前言web开发在过去的几年中有了很大的进展,我们已经远超了把静态网页链接在一起的做法,这种做法会引起浏览器的刷新,并且要等待页面的加载。现在需要的是能够通过web来访问的完全动态的应用,这些应用通常需要尽可能的快,提供近乎实时的组件。在这一新的由五部分组成的文章系列中,我们学习如何使用反向Ajax(ReverseAjax)技术来开发事件驱动的web应用。在这第一篇文章中,我们要了解反向Ajax、轮询(polling)、流(streaming)、Comet和长轮询(longpolling),学习如何实现不同的反向Ajax通信技术,并探讨每种方法的优点和缺点。你可以下载本文中例子的相应源代码。Ajax、反向Ajax和WebSocket异步的JavaScript和XML(AsynchronousJavaScriptandXML,Ajax),一种可通过JavaScript来访问的浏览器功能特性,其允许脚本向幕后的网站发送一个HTTP请求而又无需重新加载页面。Ajax的出现已经超过了十年,尽管其名字中包含了XML,但你几乎可以在Ajax请求中传送任何的东西,最常用的数据是JSON,其与JavaScript语法很接近,且消耗更少带宽。清单1给出了这样的一个例子,Ajax请求通过某个地方的邮政编码来检索该地的名称。清单1.Ajax请求举例varurl='='+$('#postalCode').val()+'&country='+$('#country').val()+'&callback=?';$.getJSON(url,function(data){$('#placeName').val(data.postalcodes[0].placeName);});在本文可下载的源代码中,你可在listing1.html中看到这一例子的作用。反向Ajax(ReverseAjax)本质上则是这样的一种概念:能够从服务器端向客户端发送数据。在一个标准的HTTPAjax请求中,数据是发送给服务器端的,反向Ajax可以某些特定的方式来模拟发出一个Ajax请求,这些方式本文都会论及,这样的话,服务器就可以尽可能快地向客户端发送事件(低延迟通信)。WebSocket技术来自HTML5,是一种最近才出现的技术,许多浏览器已经支持它(Firefox、GoogleChrome、Safari等等)。WebSocket启用双向的、全双工的通信信道,其通过某种被称为WebSocket握手的HTTP请求来打开连接,并用到了一些特殊的报头。连接保持在活动状态,你可以用JavaScript来写和接收数据,就像是正在用一个原始的TCP套接口一样。WebSocket会在这一文章系列的第二部分中谈及。反向Ajax技术反向Ajax的目的是允许服务器端向客户端推送信息。Ajax请求在缺省情况下是无状态的,且只能从客户端向服务器端发出请求。你可以通过使用技术模拟服务器端和客户端之间的响应式通信来绕过这一限制。HTTP轮询和JSONP轮询轮询(polling)涉及了从客户端向服务器端发出请求以获取一些数据,这显然就是一个纯粹的AjaxHTTP请求。为了尽快地获得服务器端事件,轮询的间隔(两次请求相隔的时间)必须尽可能地小。但有这样的一个缺点存在:如果间隔减小的话,客户端浏览器就会发出更多的请求,这些请求中的许多都不会返回任何有用的数据,而这将会白白地浪费掉带宽和处理资源。图1中的时间线说明了客户端发出了某些轮询请求,但没有信息返回这种情况,客户端必须要等到下一个轮询来获取两个服务器端接收到的事件。图1.使用HTTP轮询的反向AjaxJSONP轮询基本上与HTTP轮询一样,不同之处则是JSONP可以发出跨域请求(不是在你的域内的请求)。清单1使用JSONP来通过邮政编码获取地名,JSONP请求通常可通过它的回调参数和返回内容识别出来,这些内容是可执行的JavaScript代码。要在JavaScript中实现轮询的话,你可以使用setInterval来定期地发出Ajax请求,如清单2所示:清单2.JavaScript轮询setInterval(function(){$.getJSON('events',function(events){console.log(events);});},2000);文章源代码中的轮询演示给出了轮询方法所消耗的带宽,间隔很小,但可以看到有些请求并未返回事件,清单3给出了这一轮询示例的输出。清单3.轮询演示例子的输出[client]checkingforevents...[client]noevent[client]checkingforevents...[client]2events[event]AtSunJun0515:17:14EDT2011[event]AtSunJun0515:17:14EDT2011[client]checkingforevents...[client]1events[event]AtSunJun0515:17:16EDT2011用JavaScript实现的轮询的优点和缺点:1.优点:很容易实现,不需要任何服务器端的特定功能,且在所有的浏览器上都能工作。2.缺点:这种方法很少被用到,因为它是完全不具伸缩性的。试想一下,在100个客户端每个都发出2秒钟的轮询请求的情况下,所损失的带宽和资源数量,在这种情况下30%的请求没有返回数据。Piggyback捎带轮询(piggybackpolling)是一种比轮询更加聪明的做法,因为它会删除掉所有非必需的请求(没有返回数据的那些)。不存在时间间隔,客户端在需要的时候向服务器端发送请求。不同之处在于响应的那部分上,响应被分成两个部分:对请求数据的响应和对服务器事件的响应,如果任何一部分有发生的话。图2给出了一个例子。图2.使用了piggyback轮询的反向Ajax在实现piggyback技术时,通常针对服务器端的所有Ajax请求可能会返回一个混合的响应,文章的下载中有一个实现示例,如下面的清单4所示。清单4.piggyback代码示例$('#submit').click(function(){$.post('ajax',function(data){varvalid=data.formValid;//处理验证结果//然后处理响应的其他部分(事件)processEvents(data.events);});});清单5给出了一些piggyback输出。清单5.piggyback输出示例[client]checkingforevents...[server]formvalid?true[client]4events[event]AtSunJun0516:08:32EDT2011[event]AtSunJun0516:08:34EDT2011[event]AtSunJun0516:08:34EDT2011[event]AtSunJun0516:08:37EDT2011你可以看到表单验证的结果和附加到响应上的事件,同样,这种方法也有着一些优点和缺点:1.优点:没有不返回数据的请求,因为客户端对何时发送请求做了控制,对资源的消耗较少。该方法也是可用在所有的浏览器上,不需要服务器端的特殊功能。2.缺点:当累积在服务器端的事件需要传送给客户端时,你却一点都不知道,因为这需要一个客户端行为来请求它们。Comet使用了轮询或是捎带的反向Ajax非常受限:其不具伸缩性,不提供低延迟通信(只要事件一到达服务器端,它们就以尽可能快的速度到达浏览器端)。Comet是一个web应用模型,在该模型中,请求被发送到服务器端并保持一个很长的存活期,直到超时或是有服务器端事件发生。在该请求完成后,另一个长生存期的Ajax请求就被送去等待另一个服务器端事件。使用Comet的话,web服务器就可以在无需显式请求的情况下向客户端发送数据。Comet的一大优点是,每个客户端始终都有一个向服务器端打开的通信链路。服务器端可以通过在事件到来时立即提交(完成)响应来把事件推给客户端,或者它甚至可以累积再连续发送。因为请求长时间保持打开的状态,故服务器端需要特别的功能来处理所有的这些长生存期请求。图3给出了一个例子。(这一文章系列的第2部分会更加详细地解释服务器端的约束条件)。图3.使用Comet的反向AjaxComet的实现可以分成两类:使用流(streaming)的那些和使用长轮询(longpolling)的那些。使用HTTP流的Comet在流(streaming)模式中,有一个持久连接会被打开。只会存在一个长生存期请求(图3中的#1),因为每个到达服务器端的事件都会通过这同一连接来发送。因此,客户端需要有一种方法来把通过这同一连接发送过来的不同响应分隔开来。从技术上来讲,两种常见的流技术包括ForeverIframe(隐藏的IFrame),或是被用来在JavaScript中创建Ajax请求的XMLHttpRequest对象的多部分(multi-part)特性。ForeverIframeForeverIframe(永存的Iframe)技术涉及了一个置于页面中的隐藏Iframe标签,该标签的src属性指向返回服务器端事件的servlet路径。每次在事件到达时,servlet写入并刷新一个新的script标签,该标签内部带有JavaScript代码,iframe的内容被附加上这一script标签,标签中的内容就会得到执行。1.优点:实现简单,在所有支持iframe的浏览器上都可用。2.缺点:没有方法可用来实现可靠的错误处理或是跟踪连接的状态,因为所有的连接和数据都是由浏览器通过HTML标签来处理的,因此你没有办法知道连接何时在哪一端已被断开了。Multi-partXMLHttpRequest第二种技术,更可靠一些,是XMLHttpRequest对象上使用某些浏览器(比如说Firefox)支持的multi-part标志。Ajax请求被发送给服务器端并保持打开状态,每次有事件到来时,一个多部分的响应就会通过这同一连接来写入,清单6给出了一个例子。清单6.设置Multi-partXMLHttpRequest的JavaScript代码示例varxhr=$.ajaxSettings.xhr();xhr.multipart=true;xhr.open('GET','ajax',true);xhr.onreadystatechange=function(){if(xhr.readyState==4){processEvents($.parseJSON(xhr.responseText));}};xhr.send(null);在服务器端,事情要稍加复杂一些。首先你必须要设置多部分请求,然后挂起连接。清单7展示了如何挂起一个HTTP流请求。(这一系列的第3部分会更加详细地谈及这些API。)清单7.使用Servlet3API来在servlet中挂起一个HTTP流请求protectedvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{//开