反向ajax实现

在过去的几年中,web开发已经发生了很大的变化。现现在,咱们指望的是可以经过web快速、动态地访问应用。在这一新的文章系列中,咱们学习如何使用反 向Ajax(Reverse Ajax)技术来开发事件驱动的web应用,以此来实现更好的用户体验。客户端的例子使用的是JQuery JavaScript库,在这首篇文章中,咱们探索不一样的反向Ajax技术,使用可下载的例子来学习使用了流(streaming)方法和长轮询(long polling)方法的Comet。

  前言javascript

  web开发在过去的几年中有了很大的进展,咱们已经远超了把静态网页连接在一块儿的作法,这种作法会引发浏览器的刷新,而且要等待页面的加载。如今须要 的是可以经过web来访问的彻底动态的应用,这些应用一般须要尽量的快,提供近乎实时的组件。在这一新的由五部分组成的文章系列中,咱们学习如何使用反 向Ajax(Reverse Ajax)技术来开发事件驱动的web应用。html

  在这第一篇文章中,咱们要了解反向Ajax、轮询(polling)、流(streaming)、Comet和长轮询(long polling),学习如何实现不一样的反向Ajax通讯技术,并探讨每种方法的优势和缺点。你能够下载本文中例子的相应源代码。html5

  Ajax、反向Ajax和WebSocketjava

  异步的JavaScript和XML(Asynchronous JavaScript and XML,Ajax),一种可经过JavaScript来访问的浏览器功能特性,其容许脚本向幕后的网站发送一个HTTP请求而又无需从新加载页面。Ajax的出现已经超过了十年,尽管其名字中包含了XML,但你几乎能够在Ajax请求中传送任何的东西,最经常使用的数据是JSON,其与JavaScript语法很接近,且消耗更少带宽。清单1给出了这样的一个例子,Ajax请求经过某个地方的邮政编码来检索该地的名称。git

  清单1. Ajax请求举例github

复制代码
var url ='http://www.geonames.org/postalCodeLookupJSON?postalcode='
  + $('#postalCode').val() +'&country='
  + $('#country').val() +'&callback=?';
  $.getJSON(url, function(data) {
  $('#placeName').val(data.postalcodes[0].placeName);
});
复制代码

  在本文可下载的源代码中,你可在listing1.html中看到这一例子的做用。web

  反向Ajax(Reverse Ajax)本质上则是这样的一种概念:可以从服务器端向客户端发送数据。在一个标准的HTTP Ajax请求中,数据是发送给服务器端的,反向Ajax能够某些特定的方式来模拟发出一个Ajax请求,这些方式本文都会论及,这样的话,服务器就能够尽量快地向客户端发送事件(低延迟通讯)。ajax

  WebSocket技术来自HTML5,是一种最近才出现的技术,许多浏览器已经支持它(Firefox、Google Chrome、Safari等等)。WebSocket启 用双向的、全双工的通讯信道,其经过某种被称为WebSocket握手的HTTP请求来打开链接,并用到了一些特殊的报头。链接保持在活动状态,你能够用 JavaScript来写和接收数据,就像是正在用一个原始的TCP套接口同样。WebSocket会在这一文章系列的第二部分中谈及。apache

  反向Ajax技术json

  反向Ajax的目的是容许服务器端向客户端推送信息。Ajax请求在缺省状况下是无状态的,且只能从客户端向服务器端发出请求。你能够经过使用技术模拟服务器端和客户端之间的响应式通讯来绕过这一限制。

  HTTP轮询和JSONP轮询

  轮询(polling)涉及了从客户端向服务器端发出请求以获取一些数据,这显然就是一个纯粹的Ajax HTTP请求。为了尽快地得到服务器端事件,轮询的间隔(两次请求相隔的时间)必须尽量地小。但有这样的一个缺点存在:若是间隔减少的话,客户端浏览器就会发出更多的请求,这些请求中的许多都不会返回任何有用的数据,而这将会白白地浪费掉带宽和处理资源。

  图1中的时间线说明了客户端发出了某些轮询请求,但没有信息返回这种状况,客户端必需要等到下一个轮询来获取两个服务器端接收到的事件。

  图1. 使用HTTP轮询的反向Ajax

     

 

 

  JSONP轮询基本上与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 ] checking for events...
[client ] no event
[client ] checking for events...
[client ]2 events
[event ] At Sun Jun 0515:17:14 EDT 2011
[event ] At Sun Jun 0515:17:14 EDT 2011
[client ] checking for events...
[client ]1 events
[event ] At Sun Jun 0515:17:16 EDT 2011
复制代码

  用JavaScript实现的轮询的优势和缺点:

  1. 优势:很容易实现,不须要任何服务器端的特定功能,且在全部的浏览器上都能工做。

  2. 缺点:这种方法不多被用到,由于它是彻底不具伸缩性的。试想一下,在100个客户端每一个都发出2秒钟的轮询请求的状况下,所损失的带宽和资源数量,在这种状况下30%的请求没有返回数据。

  Piggyback

  捎带轮询(piggyback polling)是一种比轮询更加聪明的作法,由于它会删除掉全部非必需的请求(没有返回数据的那些)。不存在时间间隔,客户端在须要的时候向服务器端发送请求。不一样之处在于响应的那部分上,响应被分红两个部分:对请求数据的响应和对服务器事件的响应,若是任何一部分有发生的话。图2给出了一个例子。

  图2. 使用了piggyback轮询的反向Ajax

  在实现piggyback技术时,一般针对服务器端的全部Ajax请求可能会返回一个混合的响应,文章的下载中有一个实现示例,以下面的清单4所示。

  清单4. piggyback代码示例

复制代码
$('#submit').click(function() {
  $.post('ajax', function(data) {
    var valid = data.formValid;
    // 处理验证结果
    // 而后处理响应的其余部分(事件)
    processEvents(data.events);
  });
});
复制代码

  清单5给出了一些piggyback输出。

  清单5. piggyback输出示例

复制代码
[client ] checking for events...
[server ] form valid ? true
[client ]4 events
[event ] At Sun Jun 0516:08:32 EDT 2011
[event ] At Sun Jun 0516:08:34 EDT 2011
[event ] At Sun Jun 0516:08:34 EDT 2011
[event ] At Sun Jun 0516:08:37 EDT 2011
复制代码

  你能够看到表单验证的结果和附加到响应上的事件,一样,这种方法也有着一些优势和缺点:

  1. 优势:没有不返回数据的请求,由于客户端对什么时候发送请求作了控制,对资源的消耗较少。该方法也是可用在全部的浏览器上,不须要服务器端的特殊功能。

  2. 缺点:当累积在服务器端的事件须要传送给客户端时,你却一点都不知道,由于这须要一个客户端行为来请求它们。

  Comet

  使用了轮询或是捎带的反向Ajax很是受限:其不具伸缩性,不提供低延迟通讯(只要事件一到达服务器端,它们就以尽量快的速度到达浏览器端)。 Comet是一个web应用模型,在该模型中,请求被发送到服务器端并保持一个很长的存活期,直到超时或是有服务器端事件发生。在该请求完成后,另外一个长 生存期的Ajax请求就被送去等待另外一个服务器端事件。使用Comet的话,web服务器就能够在无需显式请求的状况下向客户端发送数据。

  Comet的一大优势是,每一个客户端始终都有一个向服务器端打开的通讯链路。服务器端能够经过在事件到来时当即提交(完成)响应来把事件推给客户端, 或者它甚至能够累积再连续发送。由于请求长时间保持打开的状态,故服务器端须要特别的功能来处理全部的这些长生存期请求。图3给出了一个例子。(这一文章 系列的第2部分会更加详细地解释服务器端的约束条件)。

  图3. 使用Comet的反向Ajax

  Comet的实现能够分红两类:使用流(streaming)的那些和使用长轮询(long polling)的那些。

  使用HTTP流的Comet

  在流(streaming)模式中,有一个持久链接会被打开。只会存在一个长生存期请求(图3中的#1),由于每一个到达服务器端的事件都会经过这同一 链接来发送。所以,客户端须要有一种方法来把经过这同一链接发送过来的不一样响应分隔开来。从技术上来说,两种常见的流技术包括Forever Iframe(隐藏的IFrame),或是被用来在JavaScript中建立Ajax请求的XMLHttpRequest对象的多部分(multi- part)特性。

  Forever Iframe

  Forever Iframe(永存的Iframe)技术涉及了一个置于页面中的隐藏Iframe标签,该标签的src属性指向返回服务器端事件的servlet路径。每 次在事件到达时,servlet写入并刷新一个新的script标签,该标签内部带有JavaScript代码,iframe的内容被附加上这一 script标签,标签中的内容就会获得执行。

  1. 优势:实现简单,在全部支持iframe的浏览器上均可用。

  2. 缺点: 没有方法可用来实现可靠的错误处理或是跟踪链接的状态,由于全部的链接和数据都是由浏览器经过HTML标签来处理的,所以你没有办法知道链接什么时候在哪一端已被断开了。

  Multi-part XMLHttpRequest

  第二种技术,更可靠一些,是XMLHttpRequest对象上使用某些浏览器(好比说Firefox)支持的multi-part标志。Ajax请 求被发送给服务器端并保持打开状态,每次有事件到来时,一个多部分的响应就会经过这同一链接来写入,清单6给出了一个例子。

  清单6. 设置Multi-part XMLHttpRequest的JavaScript代码示例

复制代码
var xhr = $.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. 使用Servlet 3 API来在servlet中挂起一个HTTP流请求

复制代码
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
  // 开始请求的挂起
  AsyncContext asyncContext = req.startAsync();
  asyncContext.setTimeout(0);

  // 给客户端发回多部分的分隔符
  resp.setContentType("multipart/x-mixed-replace;boundary=\""
  + boundary +"\"");
  resp.setHeader("Connection", "keep-alive");
  resp.getOutputStream().print("--"+ boundary);
  resp.flushBuffer();

  // 把异步上下文放在列表中以被未来只用
  asyncContexts.offer(asyncContext);
}
复制代码

  如今,每次有事件发生时你均可以遍历全部的挂起链接并向它们写入数据,如清单8所示:

  清单8. 使用Servlet 3 API来向挂起的多部分请求发送事件

复制代码
for (AsyncContext asyncContext : asyncContexts) {
  HttpServletResponse peer = (HttpServletResponse)
  asyncContext.getResponse();
  peer.getOutputStream().println("Content-Type: application/json");
  peer.getOutputStream().println();
  peer.getOutputStream().println(new JSONArray()
  .put("At "+new Date()).toString());
  peer.getOutputStream().println("--"+ boundary);
  peer.flushBuffer();
}
复制代码

  本文可下载文件的Comet-straming文件夹中的部分说明了HTTP流,在运行例子并打开主页时,你会看到只要事件一到达服务器端,虽然不一样 步但它们几乎马上会出如今页面上。并且,若是打开Firebug控制台的话,你就能看到只有一个Ajax请求是打开的。若是再往下看一些,你会看到 JSON响应被附在Response选项卡中,如图4所示:

  图4. HTTP流请求的FireBug视图

  照例,作法存在着一些优势和缺点:

  1. 优势:只打开了一个持久链接,这就是节省了大部分带宽使用率的Comet技术。

  2. 缺点:并不是全部的浏览器都支持multi-part标志。某些被普遍使用的库,好比说用Java实现的CometD,被报告在缓冲方面有问题。例如,一些 数据块(多个部分)可能被缓冲,而后只有在链接完成或是缓冲区已满时才被发送,而这有可能会带来比预期要高的延迟。

  使用HTTP长轮询的Comet

  长轮询(long polling)模式涉及了打开链接的技术。链接由服务器端保持着打开的状态,只要一有事件发生,响应就会被提交,而后链接关闭。接下来。一个新的长轮询链接就会被正在等待新事件到达的客户端从新打开。

  你可使用script标签或是单纯的XMLHttpRequest对象来实现HTTP长轮询。

  script标签

  正如iframe同样,其目标是把script标签附加到页面上以让脚本执行。服务器端则会:挂起链接直到有事件发生,接着把脚本内容发送回浏览器,而后从新打开另外一个script标签来获取下一个事件。

  1. 优势:由于是基于HTML标签的,全部这一技术很是容易实现,且可跨域工做(缺省状况下,XMLHttpRequest不容许向其余域或是子域发送请求)。

  2. 缺点:相似于iframe技术,错误处理缺失,你不能得到链接的状态或是有干涉链接的能力。

  XMLHttpRequest长轮询

  第二种,也是一种推荐的实现Comet的作法是打开一个到服务器端的Ajax请求而后等待响应。服务器端须要一些特定的功能来容许请求被挂起,只要一 有事件发生,服务器端就会在挂起的请求中送回响应并关闭该请求,彻底就像是你关闭了servlet响应的输出流。而后客户端就会使用这一响应并打开一个新 的到服务器端的长生存期的Ajax请求,如清单9所示:

  清单9. 设置长轮询请求的JavaScript代码示例

复制代码
function long_polling() {
  $.getJSON('ajax', function(events) {
    processEvents(events);
    long_polling();
  });
}
long_polling();
复制代码

  在后端,代码也是使用Servlet 3 API来挂起请求,正如HTTP流的作法同样,但你不须要全部的多部分处理代码,清单10给出了一个例子。

  清单10. 挂起一个长轮询Ajax请求

复制代码
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
  AsyncContext asyncContext = req.startAsync();
  asyncContext.setTimeout(0);
  asyncContexts.offer(asyncContext);
}
复制代码

  在接收到事件时,只是取出全部的挂起请求并完成它们,如清单11所示:

  清单11. 在有事件发生时完成长轮询Ajax请求

复制代码
while (!asyncContexts.isEmpty()) {
  AsyncContext asyncContext = asyncContexts.poll();
  HttpServletResponse peer = (HttpServletResponse)
  asyncContext.getResponse();
  peer.getWriter().write(
    new JSONArray().put("At " + new Date()).toString());
  peer.setStatus(HttpServletResponse.SC_OK);
  peer.setContentType("application/json");
  asyncContext.complete();
}
复制代码

  在附带的下载源文件中,comet-long-polling文件夹包含了一个长轮询示例web应用,你可使用 mvn jetty:run 命令来运行它。

  1. 优势:客户端很容易实现良好的错误处理系统和超时管理。这一可靠的技术还容许在与服务器端的链接之间有一个往返,即便链接是非持久的(当你的应用有许多的 客户端时,这是一件好事)。它可用在全部的浏览器上;你只须要确保所用的XMLHttpRequest对象发送到的简单的Ajax请求就能够了。

  2. 缺点:相比于其余技术来讲,不存在什么重要的缺点,像全部咱们已经讨论过的技术同样,该方法依然依赖于无状态的HTTP链接,其要求服务器端有特殊的功能来临时挂起链接。

  建议

  由于全部现代的浏览器都支持跨域资源共享(Cross-Origin Resource Share,CORS)规范,该规范容许XHR执行跨域请求,所以基于脚本的和基于iframe的技术已成为了一种过期的须要。

  把Comet作为反向Ajax的实现和使用的最好方式是经过XMLHttpRequest对象,该作法提供了一个真正的链接句柄和错误处理。考虑到不 是全部的浏览器都支持multi-part标志,且多部分流可能会遇到缓冲问题,所以建议你选择经由HTTP长轮询使用XMLHttpRequest对象 (在服务器端挂起的一个简单的Ajax请求)的Comet模式,全部支持Ajax的浏览器也都支持该种作法。

  结论

  本文提供的是反向Ajax技术的一个入门级介绍,文章探索了实现反向Ajax通讯的不一样方法,并说明了每种实现的优点和弊端。你的具体状况和应用需求 将会影响到你对最合适方法的选择。不过通常来讲,若是你想要在低延迟通讯、超时和错误检测、简易性,以及全部浏览器和平台的良好支持这几方面有一个最好的 折中的话,那就选择使用了Ajax长轮询请求的Comet。

 

这一文章系列探讨了如何使用反向Ajax(Reverse Ajax)技术来开发事件驱动的web应用,第1部份内容介绍了实现反向Ajax通讯的几种不一样方式:轮询(polling)、捎带(piggyback)以及使用了长轮询(long-polling)和流(streaming)的Comet。在本文中,咱们学习一种新的实现反向Ajax的技术:使用WebSocket,一个新的HTML5 API。WebSocket可由浏览器厂商来作本地化实现,或是经过把调用委托给隐藏的被称为FlashSocket的Flash组件这种桥接手段来实现。本文还讨论了反向Ajax技术带来的一些服务器端约束。

  前言

  时至今日,用户期待的是可经过web访问快速、动态的应用。这一文章系列展现了如何使用反向Ajax(Reverse Ajax)技术来开发事件驱动的web应用。该系列的第1部分介绍了反向Ajax、轮询(polling)、流(streaming)、Comet和长轮询(long polling)。你已经了解了Comet是如何使用HTTP长轮询的,这是可靠地实现反向Ajax的最好方式,由于现有的全部浏览器都提供支持。

  在本文中,咱们将学习如何使用WebSocket来实现反向Ajax。代码例子被用来帮助说明WebSocket、FlashSocket、服务器端约束、请求做用域(request-scoped)服务以及暂停长生存期请求等,你能够下载本文中用到的这些源代码。

  前提条件

  理想状况下,要充分体会本文的话,你应该对JavaScrpit和Java有必定的了解。本文中建立的例子是使用Google Guice来构建的,这是一个使用Java编写的依赖注入框架。若要读懂文中所谈内容,你应该要熟悉诸如Guice、Spring或是Pico一类的依赖注入框架的概念。

  若要运行本文中的例子,你还须要最新版本的Maven和JDK(参见参考资料)。

  WebSocket

  在HTML5中出现的WebSocket是一种比Comet还要新的反向Ajax技术,WebSocket启用了双向的全双工通讯信道,许多浏览器(Firefox、Google Chrome和Safari)都已对此作了支持。链接是经过一个被称为WebSocket握手的HTTP请求打开的,其用到了一些特殊的报头。链接会保持在活动状态,你可使用JavaScript来写入和接收数据,就像是在使用一个原始的TCP套接口同样。

  WebSocket URL的起始输入是ws://或是wss://(在SSL上)。

  图1中的时间线说明了使用WebSocket的通讯。一个带有特定报头的HTTP握手被发送到了服务器端,接着在服务器端或是客户端就能够经过JavaScript来使用某种套接口(socket)了,这一套接口可被用来经过事件句柄异步地接收数据。

  图1. 使用WebSocket的反向Ajax

  本文可下载的源代码中有一个WebSocket例子,在运行该例子时,你应该会看到相似清单1的输出。其说明了客户端的事件是如何发生的,以及如何会当即在客户端显示出来。当客户端发送一些数据时,服务器端回应客户端的发送行为。

  清单1. JavaScript中的WebSocket例子

[client]  WebSocket connection opened
[server] 1 events
[event] ClientID =0
[server] 1 events
[event] At Fri Jun 1721:12:01 EDT 2011
[server] 1 events
[event] From 0 : qqq
[server] 1 events
[event] At Fri Jun 1721:12:05 EDT 2011
[server] 1 events
[event] From 0 : vv

  一般状况下,在JavaScript中你会如清单2所说明的那样来使用WebSocket,若是你的浏览器支持它的话。

  清单2. JavaScript客户端例子

var  ws = new WebSocket('ws://127.0.0.1:8080/async');
ws.onopen = function() {
    // 链接被打开时调用
};
ws.onerror = function(e) {
    // 在出现错误时调用,例如在链接断掉时
};
ws.onclose = function() {
    // 在链接被关闭时调用
};
ws.onmessage = function(msg) {
    // 在服务器端向客户端发送消息时调用
    // msg.data包含了消息
};
// 这里是如何给服务器端发送一些数据
ws.send('some data');
// 关闭套接口
ws.close();

  发送和接收的数据能够是任意类型的,WebSocket可被当作是TCP套接口,所以这取决于客户端和服务器端知道要来回发送的数据是哪一种类型的。这里的例子发送的是JSON串。

  在JavaScript WebSocket对象被建立后,若是在浏览器的控制台(或是Firebug)中仔细看一下HTTP请求的话,你应该会看到WebSocket特有的报头。清单3给出了一个例子。

  清单3. HTTP请求和相应报头示例

Request URL:ws://127.0.0.1:8080/async
Request Method:GET
Status Code:101  WebSocket Protocol Handshake

Request Headers
Connection:Upgrade
Host:127.0.0.1:8080
Origin:http://localhost:8080
Sec-WebSocket-Key1:1 &1~ 33188Yd]r8dp W75q
Sec-WebSocket-Key2:17; 229 *043M 8
Upgrade:WebSocket
(Key3):B4:BB:20:37:45:3F:BC:C7

Response Headers
Connection:Upgrade
Sec-WebSocket-Location:ws://127.0.0.1:8080/async
Sec-WebSocket-Origin:http://localhost:8080
Upgrade:WebSocket
(Challenge Response):AC:23:A5:7E:5D:E5:04:6A:B5:F8:CC:E7:AB:6D:1A:39

  WebSocket握手使用全部的这些报头来验证并设置一个长生存期的链接,WebSocket的JavaScript对象还包含了两个有用的属性:

  ws.url:返回WebSocket服务器的URL
  ws.readyState:返回当前链接状态的值
  1. CONNECTING = 0
  2. OPEN = 1
  3. CLOSED = 2

  服务器端对WebSocket的处理要稍加复杂一些,如今尚未某个Java规范以一种标准的方式来支持WebSocket。要使用web容器(例如Tomcat或是Jetty)的WebSocket功能的话,你得把应用代码和容器特定的库紧密耦合在一块儿才能访问WebSocket的功能。

  示例代码的websocket文件夹中的例子使用的是Jetty的WebSocket API,由于咱们使用的是Jetty容器。清单4 给出了WebSocket的处理程序。(本系列的第3部分会使用不一样的后端WebSocket API。)

  清单4. Jetty容器的WebSocket处理程序

public  final class ReverseAjaxServlet extends WebSocketServlet {
  @Override
  protected WebSocket doWebSocketConnect(HttpServletRequest request,String protocol) {
    return [...]
  }
}

  就Jetty来讲,有几种处理WebSocket握手的方式,比较容易的一种方式是子类化Jetty的WebSocketServlet并实现doWebSocketConnect方法。该方法要求你返回Jetty的WebSocket接口的一个实例,你必需要实现该接口并返回表明了WebSocket链接的某种端点(endpoint)。清单5提供了一个例子。

  清单5. WebSocket实现示例

class  Endpoint implements WebSocket {
Outbound outbound;
@Override
publicvoid onConnect(Outbound outbound) {
  this.outbound = outbound;
}
@Override
publicvoid onMessage(byte opcode, String data) {
  // 在接收到消息时调用
  // 你一般用到的就是这一方法
}
@Override
publicvoid onFragment(boolean more, byte opcode,byte[] data, int offset, int length) {
  // 在完成一段内容时,onMessage被调用
  // 一般不在这一方法中写入东西
}
@Override
publicvoid onMessage(byte opcode, byte[] data,int offset, int length) {
  onMessage(opcode, new String(data, offset, length));
}
@Override
publicvoid onDisconnect() {
  outbound =null;
}
}

  若要向客户端发送消息的话,你要向outbound中写入消息,若是清单6所示:

  清单6. 发送消息给客户端

if  (outbound != null && outbound.isOpen()) {
  outbound.sendMessage('Hello World !');
}

  要断开并关闭到客户端的WebSocket链接的话,使用outbound.disconnect()。

  WebSocket是一种实现无延迟双向通讯的很是强大的方法,Firefox、Google Chrome、Opera和其余的现代浏览器都支持这种作法。根据jWebSocket网站的说法:

  1. Chrome从4.0.249版本开始包含本地化的WebSocket。
  2. Safari 5.x包含了本地化的WebSocket。
  3. Firefox 3.7a6和4.0b1+包含了本地化的WebSocket。
  4. Opera从10.7.9.67开始包含了本地化的WebSocket。

  欲了解更多关于jWebSocket方面的内容,请查阅参考资料。

  优势

  WebSocket功能强大、双向、低延迟,且易于处理错误,其不会像Comet长轮询那样有许多的链接,也没有Comet流所具备的一些缺点。它的API也很容易使用,无需另外的层就能够直接使用,而Comet则须要一个很好的库来处理重链接、超时、Ajax请求、确认以及选择不一样的传输(Ajax长轮询和jsonp轮询)。

  缺点

  WebSocket的缺点有这些:

  1. 是一个来自HTML5的新规范,尚未被全部的浏览器支持。

  2. 没有请求做用域(request scope),由于WebSocket是一个TCP套接口而不是一个HTTP请求,有做用域的请求服务,好比说Hibernate的SessionInViewFilter,就不太容易使用。Hibernate是一个持久性框架,其在HTTP请求的外围提供了一个过滤器。在请求开始时,其在请求线程中设定了一个上下文(包括事务和JDBC链接)边界;在请求结束时,过滤器销毁这一上下文。

  FlashSocket

  对于不支持WebSocket的浏览器来讲,有些库可以回退到FlashSocket(经由Flash的套接口)上。这些库一般会提供一样的官方WebSocket API,但他们是经过把调用委托给一个包含在网站中的隐藏的Flash组件来实现的。

  优势

  FlashSocket透明地提供了WebSocket的功能,即便是在不支持HTML5 WebSocket的浏览器上也是如此。

  缺点

  FlashSocket有着下面的这些缺点:

  1. 其须要安装Flash插件(一般状况下,全部浏览器都会有该插件)。

  2. 其要求防火墙的843端口是打开的,这样Flash组件才能发出HTTP请求来检索包含了域受权的策略文件。若是843端口是不可到达的话,则库应该有回退动做或是给出一个错误,全部的这些处理都须要一些时间(最多3秒,这取决于库),而这会下降网站的速度。

  3. 若是客户端处在某个代理服务器的后面的话,到端口843的链接可能会被拒绝。

  WebSocketJS项目提供了一种桥接方式,其要求一个至少是10版本的Flash来为Firefox 三、Inernet Explorer 8和Internet Explorer 9提供WebSocket支持。

  建议

  相比于Comet,WebSocket带来了更多的好处。在平常开发中,客户端支持的WebSocket速度更快,且产生较少的请求(从而消耗更少的带宽)。不过,因为并不是全部的浏览器都支持WebSocket,所以,对于Reverse Ajax库来讲,最好的选择就是可以检测对WebSocket的支持,而且若是不支持WebSocket的话,还可以回退到Comet(长轮询)上。

  因为这两种技术须要从全部浏览器中得到最好的作法并保持兼容性,所以个人建议是使用一个客户端的JavaScript库,该库在这些技术之上提供一个抽象层。本系列的第3和第4部份内容会探讨一些库,第5部分则是说明它们的应用。在服务器端,正以下一节内容讨论的那样,事情则会稍加复杂一些。

  服务器端的反向Ajax约束

  如今你对客户端可用的反向Ajax解决方案已经有了一个概观,让咱们再来看看服务器端的反向Ajax解决方案。到目前为止,例子使用的都还主要是客户端的JavaScript代码。在服务器端,要接受反向Ajax链接的话,相比你所熟悉的短HTTP请求,某些技术须要特定的功能来处理长生存期的链接。为了获得更好的伸缩性,应该要使用一种新的线程模型,该模型须要Java中的某个特定API来暂停请求。还有,就WebSocket来讲,你必需要正确地管理应用中用到的服务的做用域。

  线程和非阻塞I/O

  一般状况下,web服务器会把一个线程或是一个进程与每一个传入的HTTP链接关联起来。这一链接能够是持久的(保持活动),这样多个请求就能够经过这同一个链接进行了。在本文的例子中,Apache web服务器能够配置成mpm_fork或是mpm_worker模式来改变这一行为。Java web服务器(应用服务器也包括在内——这是同一回事)一般会为每一个传入的链接使用单独的一个线程。

  产生一个新的线程会带来内存的消耗和资源的浪费,由于其并不保证产生的线程会被用到。链接可能会创建起来,可是没有来自客户端或是服务器端的数据在发送。无论这一线程是否被用到,其都会消耗用于调度和上下文切换的内存和CPU资源。并且,在使用线程模式来配置服务器时,你一般须要配置一个线程池(设定处理传入链接的线程的最大数目)。若是该值配置不当,值过小的话,你最终就会遭遇线程饥饿问题;请求就会一直处于等待状态直到有线程可用来处理它们,在达到最大并发链接时,响应时间就会降低。另外一方面,配置一个高值则可会致使内存不足的异常,产生过多线程会消耗尽JVM的全部可用的堆,致使服务器崩溃。

  Java最近引入一个新的I/O API,其被称为非阻塞式的I/O。这一API使用一个选择器来避免每次有新的HTTP链接在服务器端创建时都要绑定一个线程的作法,当有数据到来时,就会有一个事件被接收,接着某个线程就被分配来处理该请求。所以,这种作法被称为每一个请求一个线程(thread-per-request)模式。其容许web服务器,好比说WebSphere和Jetty等,使用固定数量的线程来容纳并处理愈来愈多的用户链接。在相同硬件配置的状况下,在这一模式下运行的web服务器的伸缩性要比运行在每一个链接一个线程(thread-per-connection)模型下的好得多。

  在Philip McCarthy(Comet and Reverse Ajax的做者)的博客中,关于这两种线程模式的可伸缩性有一个颇有意思的衡量基准(参见参考资料中的连接)。在图2中,你会发现一样的模式:在有太多链接时,线程模式会中止工做。

  图2. 线程模式的衡量基准

  每一个链接一个线程模式(图2中的Threads)一般会有一个更好的响应时间,由于全部的线程都已启动、准备好且是等待中,但在链接的数目太高时,其会中止提供服务。在每一个请求一个线程模式(图2中的Continuations)中,线程被用来为到达的请求提供服务,链接则是经过一个NIO选择器来处理。响应时间可能会较慢一些,但线程会回收再用,所以该方案在大容量链接方面有着更好的伸缩性。

  想要了解线程在幕后是如何工做的话,能够把一个LEGO™积木块想象成是选择器,每次传入的链接到达这一LEGO积木块时,其由一个管脚来标识。LEGO积木块/选择器有着与链接数同样多的管脚(同样多的键)。那么,只须要一个线程来等待新事件的发生,而后在这些管脚上遍历就能够了。当有事情发生时,选择器线程从发生的事件中检索出键值,而后就可使用一个线程来为传入的请求提供服务。

  “Rox Java NIO Tutorial”这一教程有很好的使用Java中的NIO的例子(参见参考资料)。

  有请求做用域的服务

  许多框架都提供了服务或是过滤器(filter)来处理到达servlet的web请求,例如,某个过滤器会:

  1. 把JDBC链接绑定到某个请求线程上,这样整个请求就只用到一个链接。

  2. 在请求结束时提交所作的改变。

  另外一个例子是Google Guice(一个依赖注入库)的Guice Servlet扩展。相似于Spring,Guice可把服务绑定在请求的做用域内,一个实例至多只会为每一个新请求建立一次(参阅参考资料得到更多信息)。

  一般的作法包括了使用用户id来把从储存库中检索出来的用户对象缓存在请求中,用户id则是取自集群化的HTTP会话。在Google Guice中,你可能会有相似清单7中给出的代码。

  清单7. 请求做用域的绑定

@Provides
@RequestScoped
Member member(AuthManager authManager,
MemberRepository memberRepository) {
  return  memberRepository.findById(authManager.getCurrentUserId());
}

  当某个member被注入到类中时,Guice会尝试这从请求中获取该对象,若是没有找到的话,它就会执行储存库调用并把结果放在请求中。

  请求做用域可与除了WebSocket以外的其余任何的反向Ajax解决方案一块儿使用,任何其余的依赖于HTTP请求的解决方案,不管是短的仍是长的生存期的均可以,每一个请求都会经过servlet分发系统,过滤器都会被执行。在完成一个暂停的(长生存其)HTTP请求时,你会在这一系列的后继部分中了解到还有另外一种作法可以让请求再次经过过滤器链。

  对于WebSocket来讲,数据直接到达onMessage回调函数上,就像是在TCP套接口中的状况那样。不存在任何的HTTP请求送达这一数据,故也不存在获取或是存放做用域对象的请求上下文。所以在onMessage回调中使用须要做用域对象的服务就会失败。可下载源代码中的guice-and-websocket例子说明了如何绕过这一限制,以便仍然可在onMessage回调中使用请求做用域对象。当你运行这一例子,并在网页上点击每一个按钮来测试一个Ajax调用(有请求做用域的)、一个WebSocket调用和一个使用了模拟请求做用域的WebSocket调用时,你会获得图3所示的输出。

  图3. 使用了请求做用域服务的WebSocket处理程序

  在使用下面任一种技术时,你可能都会遇到这些问题:

  1. Spring

  2. Hibernate

  3. 任何其余须要请求做用域或是每一请求模型的框架,好比说OpenSessionInViewFilter。

  4. 任何在过滤器的内部使用ThreadLocal这一设施来指定变量的做用域为请求线程并在之后访问这些变量的系统。

  Guice有一个优雅的解决方案,如清单8所示:

  清单8. 在WebSocket的onMessage回调中模拟一个请求做用域

//  在调用doWebSocketMethod时
// 保存到请求的引用
HttpServletRequest request = [...]
Map, Object> bindings =new HashMap, Object>();
// 我有一个服务须要一个请求来获取会话
// 所以我提供一个请求,但你能够提供任何其余
// 可能须要的绑定
bindings.put(Key.get(HttpServletRequest.class), request);
ServletScopes.scopeRequest(new Callable() {
  @Override
  public Object call() throws Exception {
  // 调用你的储存库或是任何用到做用域对象的服务
    outbound.sendMessage([...]);
    return null;
  }
}, bindings).call();

  暂停长生存期请求

  若使用Comet的话,还有另外一障碍存在,那就是服务器端如何在不影响性能的状况下暂停一个长生存期请求,而后在服务器端事件到来时尽量快地恢复并完成请求呢?

  很显然,你不能简单地让请求和响应停在那里,这会引起线程饥饿和高内存消耗。暂停非阻塞式的I/O中的一个长生存期请求,在Java中这须要一个特有的API。Servlet 3.0规范提供了这样的一个API(参见本系列的第1部份内容)。清单9给出了一个例子。

  清单9. 使用Servlet 3.0来定义一个异步的servlet

<?xml  version="1.0" encoding="UTF-8"?>

<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:j2ee="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml
/ns/j2ee/web-app_3.0.xsd">

<servlet>
<servlet-name>events</servlet-name>
<servlet-class>ReverseAjaxServlet</servlet-class>
<async-supported>true</async-supported>
</servlet>

<servlet-mapping>
<servlet-name>events</servlet-name>
<url-pattern>/ajax</url-pattern>
</servlet-mapping>

</web-app>

  在定义了一个异步的servlet以后,你就可使用Servlet 3.0 API来挂起和恢复一个请求,如清单10所示:

  清单10. 挂起和恢复一个请求

AsyncContext asyncContext  = req.startAsync();
// 把asyncContext的引用保存在某处

// 而后在须要的时候,在另外一个线程中你能够恢复并完成
HttpServletResponse req =
(HttpServletResponse) asyncContext.getResponse();
req.getWriter().write("data");
req.setContentType([...]);
asyncContext.complete();

  在Servlet 3.0以前,每一个容器都有着且如今仍有着本身的机制。Jetty的延续(continuation)就是一个颇有名的例子;Java中的许多反向Ajax库都依赖于Jetty的continuation。其并不是什么精彩绝伦的作法,也不须要你的应用运行在Jetty容器上。该API的聪明之处在于其可以检测出你正在运行的容器,若是是运行在另外一个容器上,好比说Tomcat或是Grizzly,那么若是Servlet 3.0 API可用的话,就回退到Servlet 3.0 API上。这对于Comet来讲没有问题,但若是你想要利用WebSocket的优点的话,目前别无选择,只能使用容器特有的功能。

  Servlet 3.0规范尚未发布,但许多容器都已经实现了这一API,由于这也是实施反向Ajax的一种标准作法。

  结束语

  WebSocket尽管存在一些不足之处,但倒是一个功能很是强大的反向Ajax解决方案。其目前还未在全部浏览器上实现,且若是没有反向Ajax库的帮助的话,在Java服务器端并不容易使用。由于你使用的不是标准的请求-响应风格,全部你不能依赖过滤器链的做用域执行。Comet和WebSocket须要服务器端的容器特定功能,所以在使用新出的容器时,你须要注意一下,它可能没有作这方面的扩充。

  请继续关注这一系列的第3部分,该部份内容将探讨用于Comet和WebSocket的不一样的服务器端API,你还可了解到Atomsphere,这是一个反向Ajax框架。

  下载

  描述        名称         大小   下载方法

  文章的源代码   reverse_ajaxpt2_source.zip 14KB   HTTP

  参考资料

  1. “Start using HTML5 WebSockets today”(Nettuts+):重温在PHP中如何运行一个WebSocket服务器,并考虑如何构建一个客户端来经过WebSocket协议发送和接收消息。

  2. “The WebSocket API”(W3C, July 2011):这一规范定义的API使得网页可以使用WebSocket协议来和远程主机进行双向通讯。

  3. jWebSocket支持的浏览器:了解jWebSocket和Flash套接口桥所支持浏览器的各方面信息。

  4. 了解更多关于Servlet 3.0对异步处理的支持方面的内容。

  5. Philip McCarthy的博客上的文章Comet & Java: Threaded Vs Nonblocking I/O中有着更多的内容。

  6. The Rox Java NIO Tutorial这一教程收集了做者使用Java NIO库的一些经验,以及几十条的诀窍、技巧、建议和充斥着互联网的告诫作法。

  7. 在维基百科上了解这些内容:

  7.1 Ajax
  7.2 Reverse Ajax
  7.3 Comet
  7.4 WebSockets

  8. “Exploring Reverse AJAX”(Google Maps .Net Control博客,2006年8月):得到一些关于反向Ajax技术的介绍说明。

  9. “Cross-domain communications with JSONP, Part 1: Combine JSONP and jQuery to quickly build powerful mashups”(developerWorks, February 2009):了解如何把不起眼的跨域调用技术(JSONP)和一个灵活的JavaScript库(JQuery)结合在一块儿,以使人惊讶的速度构建出一些功能强大的聚合应用。

  10. “Cross-Origin Resource Sharing (CORS)”规范(W3C, July 2010):了解更多关于这一机制的内容,该机制容许XHR执行跨域请求。

  11. “Build Ajax applications with Ext JS”(developerWorks, July 2008):对大大加强了JavaScript开发的这一框架有一个大概的了解。

  12. “Compare JavaScript frameworks”(developerWorks, February 2010):对极大地加强了JavaScript开发的那些框架有一个总体的了解。

  13. “Mastering Ajax, Part 2: Make asynchronous requests with JavaScript and Ajax”(developerWorks, January 2006):学习如何使用Ajax和XMLHttpRequest对象来建立一种永不会让用户等待服务器响应的请求/响应模型。

  14. “Create Ajax applications for the mobile Web”(developerWorks, March 2010):了解如何使用Ajax构建跨浏览器的智能手机Web应用。

  15. “Where and when to use Ajax in your applications”(developerWorks, February 2008):了解如何使用Ajax来改进网站,同时避免糟糕的用户体验。

  16. “Improve the performance of Web 2.0 applications“(developerWorks, December 2009):探讨不一样的浏览器端缓存机制。

  17. “Introducing JSON”(JSON.org):得到对JSON语法的一个入门介绍。

  18. developerWorks Web development zone:得到各类谈论基于Web的解决方案的文章。

  19. developerWorks podcasts:收听各类与软件开发者进行的有趣的访谈和讨论。

  20. developerWorks technical events and webcasts:随时关注developerWorks的技术事件和webcast的进展。

  获取产品和技术

  1. WebSocketJS(WebSocket Flash Bridge):获取这一由Flash支持的HTML5 WebSocket实现。

  2. Google Guice:获取Google Guice,一个Java 5及以上版本的轻量级的依赖注入框架。

  3. Jetty:获取Jetty,一个web服务器和javax.servlet容器,外带对WebSocket的支持。

  4. Apache Maven:获取Maven,一个软件项目管理和包容工具。

  5. Java Development Kit, Version 6:得到Java平台标准版(Java Platform, Standard Edition,Java SE),该平台容许你在台式机和服务器上,以及在当今要求苛刻的嵌入式环境上开发和部署Java应用。

  6. 免费试用IBM软件,下载使用版,登陆在线试用,在沙箱环境中使用产品,或是经过云来访问,有超过100种IBM产品试用版选择。

  讨论

  1. 如今就建立你的developerWorks我的资料,并设置一个关于Reverse Ajax的观看列表。与developerWorks社区创建联系并保持联系。

  2. 找到其余在web开发方面感兴趣的developerWorks成员

  3. 分享你的知识:加入一个关注web专题的developerWorks组

  4. Roland Barcia在他的博客中谈论Web 2.0和中间件

  5. 关注developerWork成员的shared bookmarks on web topics

  6. 快速得到答案:访问Web 2.0 Apps论坛

  7. 快速得到答案:访问Ajax论坛

相关文章
相关标签/搜索