Web服务端推送技术介绍

Web服务端推送技术,作为客户端与服务器端实时交互的解决方案,经常用于一些实时性要求高的Web应用系统中。目前,经常使用的两项服务端推送技术有Comet和Websocket。

Comet技术模型

在Websocket出现之前,Comet是最主要的服务端推送技术。众所周知,超文本传输协议是一种请求/响应协议。HTTP定义了三个对象,分别是客户端,代理和服务端。客户端通过和服务端建立连接来发送HTTP请求。服务端接受来自客户端的连接,然后通过发送响应信息来服务HTTP请求。代理是中间实体,它可以用来转发传递客户端到服务端的请求,同样,也可以用来转发传递服务端到客户端的响应。

在标准的HTTP模型中,服务端不能初始化到客户端的连接,也不能发送一个未被客户端请求的响应;因而服务端不能发送异步事件到客户端。为了尽可能的接受异步事件,客户端需要周期性的去服务端拉取新的内容。然而,当服务端没有可用的新内容的时候,连续请求会消耗很多带宽资源。持续请求的效率也很低,因为它降低了应用的响应能力,在服务端接受到下一次客户端轮询请求之前,数据一直在排队。为了消除传统Web模型的局限以及依靠轮训来提高实时性的弊端,多种服务端推送编程机制被实现,这些机制被统称为“Comet”模型。

在Web开发中使用Comet技术的时间要早于Comet这个词来描述这些技术。根据技术方案的不同,Comet又被称为Ajax推送、反向Ajax、双向Web、Http流、Http服务器推送等。Comet的实现方式基本可以划分为两类:流和长轮询。

基于流的Comet

基于流的Comet是指打开一个单独的持久链接用于浏览器和服务器之间的所有Comet事件。服务器发送事件,客户端对事件进行处理,而HTTP链接不会关闭。基于流的Comet包含使用隐藏帧和XHR两种基础技术的实现。

使用隐藏帧的好处是简单而且通用性、跨浏览器支持非常好,只要支持<iframe>标签即可。但是缺点也很明显,一是没有可靠的错误处理方法,二是无法跟踪请求调用过程的状态。

XHR对象是Ajax应用中浏览器和服务器通信的主要工具。通过为XHR生成一个定制的数据格式并有JavaScript负责完成解析,它也可以用于Comet消息推送。此方案依赖于浏览器接收到新数据是触发的onreadystatechange回调。这种方式易于跟踪请求处理,但是跨浏览器的支持不如隐藏帧,因为浏览器厂商对于XHR的支持并不完全一致,如IE使用的是ActiveX对象,而在Firefox中则是一个内置的Javascript类。此外,由于浏览器的安全策略,为了避免跨站脚本攻击,XHR存在跨域访问的限制。

基于长轮询

基于流的方案,对于现在浏览器来说,都存在不可避免的负作用,迫使业界又实现了几种复杂的流传输,并根据浏览器进行切换。由此,许多Comet采用了长轮询的方式,这种方式更易于在浏览器端实现。区别于普通ajax轮询,长轮询需要客户端通过Ajax发送请求,在服务端获取数据,如果没有数据则线程等待有数据过来唤醒线程并返回数据,处理完毕后关闭链接。然后浏览器再发起新的长轮询请求以处理后续的事件。

基于长轮询的Comet包含XHR长轮询和Script标签长轮询两种实现方式。前者同样存在跨域的问题,如果没有启用跨域资源共享,此时,Comet事件是不能用于修改主页面的HTML的,这种问题可以通过设置代理服务器的方式来规避,这使得他们看起来好像源于一个域,但是这增加了部署的复杂性,并降低了访问性能,因此并不是一个好的方式。而后者在HTML中<script>标签可以只想任何URI,并且响应中Javascript代码可以在当前HTML文档中执行。因此一个基于长轮询的Comet传输可以通过动态创建<script>标签并将其源指向Comet服务器来实现。当浏览器加载新增的<script>标签时,服务端以JavaScript形式返回Comet事件,当<script>请求完成时,浏览器会再创建新的<script>标签以接收新的事件。后者的优势是跨浏览器支持,而不存在跨域的问题,但会存在潜在的风险尽管这种风险可以通过JSONP避免。

WebSocket技术

Comet是在HTTP单向通信的基础上模拟服务器与客户端浏览器的双向通信,不同的Comet方案存在不同的缺陷,无论是跨浏览器还是规范限制,而且因为是一种模拟实现,它的效率并不高(如HTTP报销头的开销,尤其是发送消息较小的情况)。

基于这些原因,人们试图从规范角度寻找一种标准的替代方案。HTML5提供了一种全新的协议来解决这个问题,这就是WebSocket,一种实现了客户端与服务器之间的全双工通信的协议,他可以更好的节省服务器资源以及带宽以提供实时通信协议。

Websocket是独立的基于TCP的协议,建立WebSocket链接时,客户端首先发送一个握手请求,服务器返回一个握手响应,握手为HTTP Upgrade请求,因此服务器可以通过HTTP端口进行处理,并将通信切换至WebSocket协议。握手成功之后,客户端与服务器之间就可以基于WebSocket协议进行全双工通信了。

WebSocket与HTTP协议完全不同,他们之间的关系仅限于WebSocket通过HTTP协议的Upgrade请求完成的。WebSocket之所以如此设计,旨在不损害网络安全的前提下解决全双工通信的问题。目前主流的浏览器均已支持WebSocket。在服务器方面主要的几款开源Servlet容器(Tomcat、jetty、Undertow)都支持WebSocket。

WebSocket协议定义了ws://和wss://两个前缀分别来表示非加密和加密链接。除去协议前缀外,其链接的具体语法格式与HTTP相同。

其中Upgrade: websocket表明这是一个WebSocket请求,Sec-WebSocket-Key是客户端发送的base64编码的密文,要求服务端必须返回一个对应加密的Sec-WebSocket-Accept头信息作为应答。

HTTP 101状态码表明服务端已经识别并切换为WebSocket协议,Sec-WebSocket-Accept是服务端采用与客户端一直的密钥计算出来的信息。

如果在与Apache或者Nginx集成的情况下使用WebSocket,通常需要进行额外的配置,具体可参见:

Apache:http://httpd.apache.org/docs/2.4/mod/mod_proxy_wstunnel.html

Nginx: http://www.ngnix.com.blog/websocket-nginx/

既然WebSocket是HTML5新增的特性,那么在使用时就要考虑浏览器旧版本兼容的问题,这也是Comet方案尽管存在各种问题但是仍旧被采用的原因。

总结

本文简单介绍Web服务端推送技术的相关内容,包括Comet和WebSocket,使用这两项技术就可以开发有实时性要求的Web应用系统。一个健壮的服务器推送方案非常复杂,远不是过几千字的文章就可以说清楚的,感兴趣的读者可以进一步阅读相关资料。

参考文献

Specifications - Apache Tomcat - Apache Software Foundation

Trust Legal Provisions (TLP) – IETF Trust

③Tomcat架构解析

④Tomcat权威指南

WebSocket协议-基础篇 - 简书

RFC 6202: Known Issues and Best Practices for the Use of Long Polling and Streaming in Bidirectional HTTP