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 两种基础技术的实现。

使用隐藏帧的好处是简单而且通用性、跨浏览器支持非常好,只要支持

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 相同。
WebSocket 握手请求格式如下:

其中 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 应用系统。一个健壮的服务器推送方案非常复杂,远不是过几千字的文章就可以说清楚的,感兴趣的读者可以进一步阅读相关资料。

参考资料
https://wiki.apache.org/tomcat/Specifications
http://trustee.ietf.org/license-info
③Tomcat 架构解析
④Tomcat 权威指南
https://www.jianshu.com/p/1b2019b02126
https://tools.ietf.org/html/rfc6202


↙↙↙阅读原文可查看相关链接,并与作者交流