Comet:基于 HTTP 长链接的“服务器推”技术(转载)

“服务器推”技术的应用javascript

传统模式的 Web 系统以客户端发出请求、服务器端响应的方式工做。这种方式并不能知足不少现实应用的需求,譬如:php

  • 监控系统:后台硬件热插拔、LED、温度、电压发生变化;
  • 即时通讯系统:其它用户登陆、发送信息;
  • 即时报价系统:后台数据库内容发生变化;

这些应用都须要服务器能实时地将更新的信息传送到客户端,而无须客户端发出请求。“服务器推”技术在现实应用中有一些解决方案,本文将这些解决方案分为两类:一类须要在浏览器端安装插件,基于套接口传送信息,或是使用 RMI、CORBA 进行远程调用;而另外一类则无须浏览器安装任何插件、基于 HTTP 长链接。html

将“服务器推”应用在 Web 程序中,首先考虑的是如何在功能有限的浏览器端接收、处理信息:java

  1. 客户端如何接收、处理信息,是否须要使用套接口或是使用远程调用。客户端呈现给用户的是 HTML 页面仍是 Java applet 或 Flash 窗口。若是使用套接口和远程调用,怎么和 JavaScript 结合修改 HTML 的显示。
  2. 客户与服务器端通讯的信息格式,采起怎样的出错处理机制。
  3. 客户端是否须要支持不一样类型的浏览器如 IE、Firefox,是否须要同时支持 Windows 和 Linux 平台。

回页首web

基于客户端套接口的“服务器推”技术ajax

Flash XMLSocket数据库

若是 Web 应用的用户接受应用只有在安装了 Flash 播放器才能正常运行, 那么使用 Flash 的 XMLSocket 也是一个可行的方案。编程

这种方案实现的基础是:浏览器

  1. Flash 提供了 XMLSocket 类。
  2. JavaScript 和 Flash 的紧密结合:在 JavaScript 能够直接调用 Flash 程序提供的接口。

具体实现方法:在 HTML 页面中内嵌入一个使用了 XMLSocket 类的 Flash 程序。JavaScript 经过调用此 Flash 程序提供的套接口接口与服务器端的套接口进行通讯。JavaScript 在收到服务器端以 XML 格式传送的信息后能够很容易地控制 HTML 页面的内容显示。服务器

关于如何去构建充当了 JavaScript 与 Flash XMLSocket 桥梁的 Flash 程序,以及如何在 JavaScript 里调用 Flash 提供的接口,咱们能够参考 AFLAX(Asynchronous Flash and XML)项目提供的 Socket Demo 以及 SocketJS(请参见 参考资源)。

Javascript 与 Flash 的紧密结合,极大加强了客户端的处理能力。从 Flash 播放器 V7.0.19 开始,已经取消了 XMLSocket 的端口必须大于 1023 的限制。Linux 平台也支持 Flash XMLSocket 方案。但此方案的缺点在于:

  1. 客户端必须安装 Flash 播放器;
  2. 由于 XMLSocket 没有 HTTP 隧道功能,XMLSocket 类不能自动穿过防火墙;
  3. 由于是使用套接口,须要设置一个通讯端口,防火墙、代理服务器也可能对非 HTTP 通道端口进行限制;

不过这种方案在一些网络聊天室,网络互动游戏中已获得普遍使用。

Java Applet 套接口

在客户端使用 Java Applet,经过 java.net.Socket 或 java.net.DatagramSocket 或 java.net.MulticastSocket 创建与服务器端的套接口链接,从而实现“服务器推”。

这种方案最大的不足在于 Java applet 在收到服务器端返回的信息后,没法经过 JavaScript 去更新 HTML 页面的内容。

回页首

基于 HTTP 长链接的“服务器推”技术

Comet 简介

浏览器做为 Web 应用的前台,自身的处理功能比较有限。浏览器的发展须要客户端升级软件,同时因为客户端浏览器软件的多样性,在某种意义上,也影响了浏览器新技术的推广。在 Web 应用中,浏览器的主要工做是发送请求、解析服务器返回的信息以不一样的风格显示。AJAX 是浏览器技术发展的成果,经过在浏览器端发送异步请求,提升了单用户操做的响应性。但 Web 本质上是一个多用户的系统,对任何用户来讲,能够认为服务器是另一个用户。现有 AJAX 技术的发展并不能解决在一个多用户的 Web 应用中,将更新的信息实时传送给客户端,从而用户可能在“过期”的信息下进行操做。而 AJAX 的应用又使后台数据更新更加频繁成为可能。


图 1. 传统的 Web 应用模型与基于 AJAX 的模型之比较
图 1. 传统的 Web 应用模型与基于 AJAX 的模型之比较 

“服务器推”是一种很早就存在的技术,之前在实现上主要是经过客户端的套接口,或是服务器端的远程调用。由于浏览器技术的发展比较缓慢,没有为“服务器推”的实现提供很好的支持,在纯浏览器的应用中很难有一个完善的方案去实现“服务器推”并用于商业程序。最近几年,由于 AJAX 技术的普及,以及把 IFrame 嵌在“htmlfile“的 ActiveX 组件中能够解决 IE 的加载显示问题,一些受欢迎的应用如 meebo,gmail+gtalk 在实现中使用了这些新技术;同时“服务器推”在现实应用中确实存在不少需求。由于这些缘由,基于纯浏览器的“服务器推”技术开始受到较多关注,Alex Russell(Dojo Toolkit 的项目 Lead)称这种基于 HTTP 长链接、无须在浏览器端安装插件的“服务器推”技术为“Comet”。目前已经出现了一些成熟的 Comet 应用以及各类开源框架;一些 Web 服务器如 Jetty 也在为支持大量并发的长链接进行了不少改进。关于 Comet 技术最新的发展情况请参考关于 Comet 的 wiki。

下面将介绍两种 Comet 应用的实现模型。

基于 AJAX 的长轮询(long-polling)方式

如 图 1 所示,AJAX 的出现使得 JavaScript 能够调用 XMLHttpRequest 对象发出 HTTP 请求,JavaScript 响应处理函数根据服务器返回的信息对 HTML 页面的显示进行更新。使用 AJAX 实现“服务器推”与传统的 AJAX 应用不一样之处在于:

  1. 服务器端会阻塞请求直到有数据传递或超时才返回。
  2. 客户端 JavaScript 响应处理函数会在处理完服务器返回的信息后,再次发出请求,从新创建链接。
  3. 当客户端处理接收的数据、从新创建链接时,服务器端可能有新的数据到达;这些信息会被服务器端保存直到客户端从新创建链接,客户端会一次把当前服务器端全部的信息取回。

图 2. 基于长轮询的服务器推模型
图 2. 基于长轮询的服务器推模型 

一些应用及示例如 “Meebo”, “Pushlet Chat” 都采用了这种长轮询的方式。相对于“轮询”(poll),这种长轮询方式也能够称为“拉”(pull)。由于这种方案基于 AJAX,具备如下一些优势:请求异步发出;无须安装插件;IE、Mozilla FireFox 都支持 AJAX。

在这种长轮询方式下,客户端是在 XMLHttpRequest 的 readystate 为 4(即数据传输结束)时调用回调函数,进行信息处理。当 readystate 为 4 时,数据传输结束,链接已经关闭。Mozilla Firefox 提供了对 Streaming AJAX 的支持, 即 readystate 为 3 时(数据仍在传输中),客户端能够读取数据,从而无须关闭链接,就能读取处理服务器端返回的信息。IE 在 readystate 为 3 时,不能读取服务器返回的数据,目前 IE 不支持基于 Streaming AJAX。

基于 Iframe 及 htmlfile 的流(streaming)方式

iframe 是很早就存在的一种 HTML 标记, 经过在 HTML 页面里嵌入一个隐蔵帧,而后将这个隐蔵帧的 SRC 属性设为对一个长链接的请求,服务器端就能源源不断地往客户端输入数据。


图 3. 基于流方式的服务器推模型
图 3. 基于流方式的服务器推模型 

上节提到的 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 浏览器,能够做为参考。(请参见 参考资源

回页首

使用 Comet 模型开发本身的应用

上面介绍了两种基于 HTTP 长链接的“服务器推”架构,更多描述了客户端处理长链接的技术。对于一个实际的应用而言,系统的稳定性和性能是很是重要的。将 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 链接

使用长链接时,存在一个很常见的场景:客户端网页须要关闭,而服务器端还处在读取数据的堵塞状态,客户端须要及时通知服务器端关闭数据链接。服务器在收到关闭请求后首先要从读取数据的阻塞状态唤醒,而后释放为这个客户端分配的资源,再关闭链接。

因此在设计上,咱们须要使客户端的控制请求和数据请求使用不一样的 HTTP 链接,才能使控制请求不会被阻塞。

在实现上,若是是基于 iframe 流方式的长链接,客户端页面须要使用两个 iframe,一个是控制帧,用于往服务器端发送控制请求,控制请求能很快收到响应,不会被堵塞;一个是显示帧,用于往服务器端发送长链接请求。若是是基于 AJAX 的长轮询方式,客户端能够异步地发出一个 XMLHttpRequest 请求,通知服务器端关闭数据链接。

在客户和服务器之间保持“心跳”信息

在浏览器与服务器之间维持一个长链接会为通讯带来一些不肯定性:由于数据传输是随机的,客户端不知道什么时候服务器才有数据传送。服务器端须要确保当客户端再也不工做时,释放为这个客户端分配的资源,防止内存泄漏。所以须要一种机制使双方知道你们都在正常运行。在实现上:

  1. 服务器端在阻塞读时会设置一个时限,超时后阻塞读调用会返回,同时发给客户端没有新数据到达的心跳信息。此时若是客户端已经关闭,服务器往通道写数据会出现异常,服务器端就会及时释放为这个客户端分配的资源。
  2. 若是客户端使用的是基于 AJAX 的长轮询方式;服务器端返回数据、关闭链接后,通过某个时限没有收到客户端的再次请求,会认为客户端不能正常工做,会释放为这个客户端分配、维护的资源。
  3. 当服务器处理信息出现异常状况,须要发送错误信息通知客户端,同时释放资源、关闭链接。

Pushlet - 开源 Comet 框架

Pushlet 是一个开源的 Comet 框架,在设计上有不少值得借鉴的地方,对于开发轻量级的 Comet 应用颇有参考价值。

观察者模型

Pushlet 使用了观察者模型:客户端发送请求,订阅感兴趣的事件;服务器端为每一个客户端分配一个会话 ID 做为标记,事件源会把新产生的事件以多播的方式发送到订阅者的事件队列里。

客户端 JavaScript 库

pushlet 提供了基于 AJAX 的 JavaScript 库文件用于实现长轮询方式的“服务器推”;还提供了基于 iframe 的 JavaScript 库文件用于实现流方式的“服务器推”。

JavaScript 库作了不少封装工做:

  1. 定义客户端的通讯状态:STATE_ERRORSTATE_ABORTSTATE_NULLSTATE_READYSTATE_JOINEDSTATE_LISTENING
  2. 保存服务器分配的会话 ID,在创建链接以后的每次请求中会附上会话 ID 代表身份;
  3. 提供了 join()leave()subscribe()、 unsubsribe()listen() 等 API 供页面调用;
  4. 提供了处理响应的 JavaScript 函数接口 onData()onEvent()

网页能够很方便地使用这两个 JavaScript 库文件封装的 API 与服务器进行通讯。

客户端与服务器端通讯信息格式

pushlet 定义了一套客户与服务器通讯的信息格式,使用 XML 格式。定义了客户端发送请求的类型:joinleavesubscribeunsubscribelistenrefresh;以及响应的事件类型:datajoin_acklisten_ackrefreshheartbeaterrorabortsubscribe_ackunsubscribe_ack

服务器端事件队列管理

pushlet 在服务器端使用 Java Servlet 实现,其数据结构的设计框架仍可适用于 PHP、C 编写的后台客户端。

Pushlet 支持客户端本身选择使用流、拉(长轮询)、轮询方式。服务器端根据客户选择的方式在读取事件队列(fetchEvents)时进行不一样的处理。“轮询”模式下 fetchEvents() 会立刻返回。”流“和”拉“模式使用阻塞的方式读事件,若是超时,会发给客户端发送一个没有新信息收到的“heartbeat“事件,若是是“拉”模式,会把“heartbeat”与“refresh”事件一块儿传给客户端,通知客户端从新发出请求、创建链接。

客户服务器之间的会话管理

服务端在客户端发送 join 请求时,会为客户端分配一个会话 ID, 并传给客户端,而后客户端就经过此会话 ID 标明身份发出subscribe 和 listen 请求。服务器端会为每一个会话维护一个订阅的主题集合、事件队列。

服务器端的事件源会把新产生的事件以多播的方式发送到每一个会话(即订阅者)的事件队列里。

回页首

小结

本文介绍了如何在现有的技术基础上选择合适的方案开发一个“服务器推”的应用,最优的方案仍是取决于应用需求的自己。相对于传统的 Web 应用, 目前开发 Comet 应用仍是具备必定的挑战性。

“服务器推”存在普遍的应用需求,为了使 Comet 模型适用于大规模的商业应用,以及方便用户构建 Comet 应用,最近几年,不管是服务器仍是浏览器都出现了不少新技术,同时也出现了不少开源的 Comet 框架、协议。需求推进技术的发展,相信 Comet 的应用会变得和 AJAX 同样普及。


参考资料

学习

本文转载地址: http://www.ibm.com/developerworks/cn/web/wa-lo-comet/
相关文章
相关标签/搜索