基于 HTTP 长链接、无须在浏览器端安装插件的“服务器推”技术为“Comet”。javascript
下面将介绍两种 Comet 应用的实现模型。html
AJAX 的出现使得 JavaScript 能够调用 XMLHttpRequest 对象发出 HTTP 请求,JavaScript 响应处理函数根据服务器返回的信息对 HTML 页面的显示进行更新。使用 AJAX 实现“服务器推”与传统的 AJAX 应用不一样之处在于:java
一些应用及示例如 “Meebo”, “Pushlet Chat” 都采用了这种长轮询的方式。相对于“轮询”(poll),这种长轮询方式也能够称为“拉”(pull)。由于这种方案基于 AJAX,具备如下一些优势:请求异步发出;无须安装插件;IE、Mozilla FireFox 都支持 AJAX。web
在这种长轮询方式下,客户端是在 XMLHttpRequest 的 readystate 为 4(即数据传输结束)时调用回调函数,进行信息处理。当 readystate 为 4 时,数据传输结束,链接已经关闭。Mozilla Firefox 提供了对 Streaming AJAX 的支持, 即 readystate 为 3 时(数据仍在传输中),客户端能够读取数据,从而无须关闭链接,就能读取处理服务器端返回的信息。IE 在 readystate 为 3 时,不能读取服务器返回的数据,目前 IE 不支持基于 Streaming AJAX。浏览器
iframe 是很早就存在的一种 HTML 标记, 经过在 HTML 页面里嵌入一个隐蔵帧,而后将这个隐蔵帧的 SRC 属性设为对一个长链接的请求,服务器端就能源源不断地往客户端输入数据。服务器
上节提到的 AJAX 方案是在 JavaScript 里处理 XMLHttpRequest 从服务器取回的数据,而后 Javascript 能够很方便的去控制 HTML 页面的显示。一样的思路用在 iframe 方案的客户端,iframe 服务器端并不返回直接显示在页面的数据,而是返回对客户端 Javascript 函数的调用,如“<script type="text/javascript">js_func(“data from server ”)</script>
”。服务器端将返回的数据做为客户端 JavaScript 函数的参数传递;客户端浏览器的 Javascript 引擎在收到服务器返回的 JavaScript 调用时就会去执行代码。架构
从 图 3 能够看到,每次数据传送不会关闭链接,链接只会在通讯出现错误时,或是链接重建时关闭(一些防火墙常被设置为丢弃过长的链接, 服务器端能够设置一个超时时间, 超时后通知客户端从新创建链接,并关闭原来的链接)。并发
使用 iframe 请求一个长链接有一个很明显的不足之处:IE、Morzilla Firefox 下端的进度栏都会显示加载没有完成,并且 IE 上方的图标会不停的转动,表示加载正在进行。Google 的天才们使用一个称为“htmlfile”的 ActiveX 解决了在 IE 中的加载显示问题,并将这种方法用到了 gmail+gtalk 产品中。Alex Russell 在 “What else is burried down in the depth's of Google's amazing JavaScript?”文章中介绍了这种方法。Zeitoun 网站提供的 comet-iframe.tar.gz,封装了一个基于 iframe 和 htmlfile 的 JavaScript comet 对象,支持 IE、Mozilla Firefox 浏览器,能够做为参考。(请参见参考资源)负载均衡
上面介绍了两种基于 HTTP 长链接的“服务器推”架构,更多描述了客户端处理长链接的技术。对于一个实际的应用而言,系统的稳定性和性能是很是重要的。将 HTTP 长链接用于实际应用,不少细节须要考虑。异步
咱们使用 IE 下载文件时会有这样的体验,从同一个 Web 服务器下载文件,最多只能有两个文件同时被下载。第三个文件的下载会被阻塞,直到前面下载的文件下载完毕。这是由于 HTTP 1.1 规范中规定,客户端不该该与服务器端创建超过两个的 HTTP 链接, 新的链接会被阻塞。而 IE 在实现中严格遵照了这种规定。
HTTP 1.1 对两个长链接的限制,会对使用了长链接的 Web 应用带来以下现象:在客户端若是打开超过两个的 IE 窗口去访问同一个使用了长链接的 Web 服务器,第三个 IE 窗口的 HTTP 请求被前两个窗口的长链接阻塞。
因此在开发长链接的应用时, 必须注意在使用了多个 frame 的页面中,不要为每一个 frame 的页面都创建一个 HTTP 长链接,这样会阻塞其它的 HTTP 请求,在设计上考虑让多个 frame 的更新共用一个长链接。
通常 Web 服务器会为每一个链接建立一个线程,若是在大型的商业应用中使用 Comet,服务器端须要维护大量并发的长链接。在这种应用背景下,服务器端须要考虑负载均衡和集群技术;或是在服务器端为长链接做一些改进。
应用和技术的发展老是带来新的需求,从而推进新技术的发展。HTTP 1.1 与 1.0 规范有一个很大的不一样:1.0 规范下服务器在处理完每一个 Get/Post 请求后会关闭套接口链接; 而 1.1 规范下服务器会保持这个链接,在处理两个请求的间隔时间里,这个链接处于空闲状态。 Java 1.4 引入了支持异步 IO 的 java.nio 包。当链接处于空闲时,为这个链接分配的线程资源会返还到线程池,能够供新的链接使用;当原来处于空闲的链接的客户发出新的请求,会从线程池里分配一个线程资源处理这个请求。 这种技术在链接处于空闲的机率较高、并发链接数目不少的场景下对于下降服务器的资源负载很是有效。
可是 AJAX 的应用使请求的出现变得频繁,而 Comet 则会长时间占用一个链接,上述的服务器模型在新的应用背景下会变得很是低效,线程池里有限的线程数甚至可能会阻塞新的链接。Jetty 6 Web 服务器针对 AJAX、Comet 应用的特色进行了不少创新的改进,请参考文章“AJAX,Comet and Jetty”(请参见 参考资源)。
使用长链接时,存在一个很常见的场景:客户端网页须要关闭,而服务器端还处在读取数据的堵塞状态,客户端须要及时通知服务器端关闭数据链接。服务器在收到关闭请求后首先要从读取数据的阻塞状态唤醒,而后释放为这个客户端分配的资源,再关闭链接。
因此在设计上,咱们须要使客户端的控制请求和数据请求使用不一样的 HTTP 链接,才能使控制请求不会被阻塞。
在实现上,若是是基于 iframe 流方式的长链接,客户端页面须要使用两个 iframe,一个是控制帧,用于往服务器端发送控制请求,控制请求能很快收到响应,不会被堵塞;一个是显示帧,用于往服务器端发送长链接请求。若是是基于 AJAX 的长轮询方式,客户端能够异步地发出一个 XMLHttpRequest 请求,通知服务器端关闭数据链接。
在浏览器与服务器之间维持一个长链接会为通讯带来一些不肯定性:由于数据传输是随机的,客户端不知道什么时候服务器才有数据传送。服务器端须要确保当客户端再也不工做时,释放为这个客户端分配的资源,防止内存泄漏。所以须要一种机制使双方知道你们都在正常运行。在实现上: