Comet:基于 HTTP 长连接的“服务器推”技术
当前位置:点晴教程→知识管理交流
→『 技术文档交流 』
很多应用譬如监控、即时通信、即时报价系统都需要将后台发生的变化实时传送到客户端而无须客户端不停地刷新、发送请求。本文首先介绍、比较了常用的“服务器推”方案,着重介绍了 comet - 使用 http 长连接、无须浏览器安装插件的两种“服务器推”方案:基于 ajax 的长轮询方式;基于 iframe 及 htmlfile 的流方式。最后分析了开发 comet 应用需要注意的一些问题,以及如何借助开源的 comet 框架-pushlet 构建自己的“服务器推”应用。 “服务器推”技术的应用 传统模式的 web 系统以客户端发出请求、服务器端响应的方式工作。这种方式并不能满足很多现实应用的需求,譬如: 监控系统:后台硬件热插拔、led、温度、电压发生变化; 将“服务器推”应用在 web 程序中,首先考虑的是如何在功能有限的浏览器端接收、处理信息: 客户端如何接收、处理信息,是否需要使用套接口或是使用远程调用。客户端呈现给用户的是 html 页面还是 java applet 或 flash 窗口。如果使用套接口和远程调用,怎么和 javascript 结合修改 html 的显示。 flash xmlsocket 如果 web 应用的用户接受应用只有在安装了 flash 播放器才能正常运行, 那么使用 flash 的 xmlsocket 也是一个可行的方案。 这种方案实现的基础是: flash 提供了 xmlsocket 类。 关于如何去构建充当了 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 方案。但此方案的缺点在于: 客户端必须安装 flash 播放器; 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 的应用又使后台数据更新更加频繁成为可能。
下面将介绍两种 comet 应用的实现模型。 基于 ajax 的长轮询(long-polling)方式 如 图 1 所示,ajax 的出现使得 javascript 可以调用 xmlhttprequest 对象发出 http 请求,javascript 响应处理函数根据服务器返回的信息对 html 页面的显示进行更新。使用 ajax 实现“服务器推”与传统的 ajax 应用不同之处在于: 服务器端会阻塞请求直到有数据传递或超时才返回。 图 2. 基于长轮询的服务器推模型
在这种长轮询方式下,客户端是在 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 可以看到,每次数据传送不会关闭连接,连接只会在通信出现错误时,或是连接重建时关闭(一些防火墙常被设置为丢弃过长的连接, 服务器端可以设置一个超时时间, 超时后通知客户端重新建立连接,并关闭原来的连接)。 使用 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 请求,通知服务器端关闭数据连接。 在客户和服务器之间保持“心跳”信息 在浏览器与服务器之间维持一个长连接会为通信带来一些不确定性:因为数据传输是随机的,客户端不知道何时服务器才有数据传送。服务器端需要确保当客户端不再工作时,释放为这个客户端分配的资源,防止内存泄漏。因此需要一种机制使双方知道大家都在正常运行。在实现上: 服务器端在阻塞读时会设置一个时限,超时后阻塞读调用会返回,同时发给客户端没有新数据到达的心跳信息。此时如果客户端已经关闭,服务器往通道写数据会出现异常,服务器端就会及时释放为这个客户端分配的资源。 pushlet 是一个开源的 comet 框架,在设计上有很多值得借鉴的地方,对于开发轻量级的 comet 应用很有参考价值。 观察者模型 pushlet 使用了观察者模型:客户端发送请求,订阅感兴趣的事件;服务器端为每个客户端分配一个会话 id 作为标记,事件源会把新产生的事件以多播的方式发送到订阅者的事件队列里。 客户端 javascript 库 pushlet 提供了基于 ajax 的 javascript 库文件用于实现长轮询方式的“服务器推”;还提供了基于 iframe 的 javascript 库文件用于实现流方式的“服务器推”。 javascript 库做了很多封装工作: 定义客户端的通信状态:state_error、state_abort、state_null、state_ready、state_joined、state_listening; 客户端与服务器端通信信息格式 pushlet 定义了一套客户与服务器通信的信息格式,使用 xml 格式。定义了客户端发送请求的类型:join、leave、subscribe、unsubscribe、listen、refresh;以及响应的事件类型:data、join_ack、listen_ack、refresh、heartbeat、error、abort、subscribe_ack、unsubscribe_ack。 服务器端事件队列管理 pushlet 在服务器端使用 java servlet 实现,其数据结构的设计框架仍可适用于 php、c 编写的后台客户端。 pushlet 支持客户端自己选择使用流、拉(长轮询)、轮询方式。服务器端根据客户选择的方式在读取事件队列(fetchevents)时进行不同的处理。“轮询”模式下 fetchevents() 会马上返回。”流“和”拉“模式使用阻塞的方式读事件,如果超时,会发给客户端发送一个没有新信息收到的“heartbeat“事件,如果是“拉”模式,会把“heartbeat”与“refresh”事件一起传给客户端,通知客户端重新发出请求、建立连接。 客户服务器之间的会话管理 服务端在客户端发送 join 请求时,会为客户端分配一个会话 id, 并传给客户端,然后客户端就通过此会话 id 标明身份发出 subscribe 和 listen 请求。服务器端会为每个会话维护一个订阅的主题集合、事件队列。 服务器端的事件源会把新产生的事件以多播的方式发送到每个会话(即订阅者)的事件队列里。 小结 本文介绍了如何在现有的技术基础上选择合适的方案开发一个“服务器推”的应用,最优的方案还是取决于应用需求的本身。相对于传统的 web 应用, 目前开发 comet 应用还是具有一定的挑战性。 “服务器推”存在广泛的应用需求,为了使 comet 模型适用于大规模的商业应用,以及方便用户构建 comet 应用,最近几年,无论是服务器还是浏览器都出现了很多新技术,同时也出现了很多开源的 comet 框架、协议。需求推动技术的发展,相信 comet 的应用会变得和 ajax 一样普及。 关于作者
周婷,软件工程师,目前在 ibm 中国软件开发技术实验室从事刀片服务器管理固件的开发工作。您可以通过
本文来自csdn博客,转载请标明出处: 该文章在 2023/12/17 23:58:04 编辑过 |
关键字查询
相关文章
正在查询... |