英文原文:Reverse Ajax, Part 2: WebSocketsjavascript
前言html
时至今日,用户期待的是可经过web访问快速、动态的应用。这一文章系列展现了如何使用反向Ajax(Reverse Ajax)技术来开发事件驱动的web应用。该系列的第1部分介绍了反向Ajax、轮询(polling)、流(streaming)、Comet和长轮询(long polling)。你已经了解了Comet是如何使用HTTP长轮询的,这是可靠地实现反向Ajax的最好方式,由于现有的全部浏览器都提供支持。html5
在本文中,咱们将学习如何使用WebSocket来实现反向Ajax。代码例子被用来帮助说明WebSocket、FlashSocket、服务器端约束、请求做用域(request-scoped)服务以及暂停长生存期请求等,你能够下载本文中用到的这些源代码。java
前提条件git
理想状况下,要充分体会本文的话,你应该对JavaScrpit和Java有必定的了解。本文中建立的例子是使用Google Guice来构建的,这是一个使用Java编写的依赖注入框架。若要读懂文中所谈内容,你应该要熟悉诸如Guice、Spring或是Pico一类的依赖注入框架的概念。github
若要运行本文中的例子,你还须要最新版本的Maven和JDK(参见参考资料)。web
WebSocketajax
在HTML5中出现的WebSocket是一种比Comet还要新的反向Ajax技术,WebSocket启用了双向的全双工通讯信道,许多浏览器(Firefox、Google Chrome和Safari)都已对此作了支持。链接是经过一个被称为WebSocket握手的HTTP请求打开的,其用到了一些特殊的报头。链接会保持在活动状态,你可使用JavaScript来写入和接收数据,就像是在使用一个原始的TCP套接口同样。apache
WebSocket URL的起始输入是ws://或是wss://(在SSL上)。json
图1中的时间线说明了使用WebSocket的通讯。一个带有特定报头的HTTP握手被发送到了服务器端,接着在服务器端或是客户端就能够经过JavaScript来使用某种套接口(socket)了,这一套接口可被用来经过事件句柄异步地接收数据。
图1. 使用WebSocket的反向Ajax
本文可下载的源代码中有一个WebSocket例子,在运行该例子时,你应该会看到相似清单1的输出。其说明了客户端的事件是如何发生的,以及如何会当即在客户端显示出来。当客户端发送一些数据时,服务器端回应客户端的发送行为。
清单1. JavaScript中的WebSocket例子
一般状况下,在JavaScript中你会如清单2所说明的那样来使用WebSocket,若是你的浏览器支持它的话。
清单2. JavaScript客户端例子
发送和接收的数据能够是任意类型的,WebSocket可被当作是TCP套接口,所以这取决于客户端和服务器端知道要来回发送的数据是哪一种类型的。这里的例子发送的是JSON串。
在JavaScript WebSocket对象被建立后,若是在浏览器的控制台(或是Firebug)中仔细看一下HTTP请求的话,你应该会看到WebSocket特有的报头。清单3给出了一个例子。
清单3. HTTP请求和相应报头示例
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处理程序
就Jetty来讲,有几种处理WebSocket握手的方式,比较容易的一种方式是子类化Jetty的WebSocketServlet并实现doWebSocketConnect方法。该方法要求你返回Jetty的WebSocket接口的一个实例,你必需要实现该接口并返回表明了WebSocket链接的某种端点(endpoint)。清单5提供了一个例子。
清单5. WebSocket实现示例
若要向客户端发送消息的话,你要向outbound中写入消息,若是清单6所示:
清单6. 发送消息给客户端
要断开并关闭到客户端的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. 请求做用域的绑定
当某个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回调中模拟一个请求做用域
暂停长生存期请求
若使用Comet的话,还有另外一障碍存在,那就是服务器端如何在不影响性能的状况下暂停一个长生存期请求,而后在服务器端事件到来时尽量快地恢复并完成请求呢?
很显然,你不能简单地让请求和响应停在那里,这会引起线程饥饿和高内存消耗。暂停非阻塞式的I/O中的一个长生存期请求,在Java中这须要一个特有的API。Servlet 3.0规范提供了这样的一个API(参见本系列的第1部份内容)。清单9给出了一个例子。
清单9. 使用Servlet 3.0来定义一个异步的servlet
在定义了一个异步的servlet以后,你就可使用Servlet 3.0 API来挂起和恢复一个请求,如清单10所示:
清单10. 挂起和恢复一个请求
在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论坛。