WebSockets 与 Bayeux/CometD

  • Alex Blewitt
  • 李明(nasi)

2010 年 5 月 30 日

话题:JavaSOA敏捷.NETRubyWeb框架JavaScript架构云计算DevOps语言 & 开发文化 & 方法

此时有两种技术可以将通信引入基于浏览器的应用之中:Bayeux(又称 CometD)和 WebSockets。究竟接下来会是一方被另一方所取代,还是双方藉由显著的差异化实现共同发展呢?

CometD框架是Bayeux协议的实现,可以使得服务器端和客户端在不可靠的网络上进行多通道异步通信。该实现用到了多种语言(JavaScriptJavaPerl等等),但是主要还是基于浏览器的 AJAX 应用。Bayeux 的优势在于,它可以运行在任何支持 AJAX 的浏览器上,在现有 HTTP 通信机制下,就能够让浏览器支持异步后台的更新,比如类似于 Google 邮件的新邮件通知的新信息送达。事实上,同样的协议还可以用于使用其他语言在不可靠的网络上连接设备进行通信(比如移动设备)。

WebSockets 是一个标准草案,这项草案由 Google、Apple 和其他进行HTML 5标准化的WhatWG工作组成员所资助。因此,支持 HTML 5 的浏览器(Chrome 或者 Safari)已经开始支持内建的 WebSocket 协议。

这两个协议的目标都是让基于 Web 的 AJAX 应用能通过异步消息或者基于 Socket 的连接进行通信,而不是在一个现有应用之上再搭建一个自己实现的通信层。这使得在设计应用的时候,可以只关注于组件部分,而把消息传递给通信层去递送。另外,这两个协议都能够建立长连接,事件可以通过长连接异步地递送给应用。这没什么新鲜的:HTTP 1.1 就支持连接管道(可以在每个请求之后保持连接,并可以在第一个请求得到处理之后再发送多个请求);而像 IMAP 等一些协议则支持IDLE命令,把连接置成休眠状态,这样在休眠的连接上就不再有数据传输,但是服务器依然可以随时推送新消息。其实在 Bayeux 或者 WebSockets 之前,通过 HTTP 进行持久通信的机制一般被称为“HTTP 推送”。

然而,长连接并不是没有任何问题。一条连接如果长时间没有数据通信的话,会被认为已经死掉,并在接下来的某个时间点被终止。为了解决这个问题,IMAP 的 IDLE 建议客户端每 29 分钟发送一个 IDLE 命令来避免断连。而 HTTP 代理则会决定连接是处于闲置状态并丢弃连接,而不去管客户端和服务器端是否已经保持了一条长连接。

资源限制也是一个问题。通常,浏览器都会限制对单个服务器的并发 HTTP 连接的数量,以避免对该服务器(或者网络连接)造成过大的压力。浏览器一般会将这个并发连接数量限制在每次 2 到 4 个。

Bayeux 和 WebSockets 都试图避免资源限制问题,使用回退机制来实现长轮询(比如 Bayeux),或者切换到其他非 HTTP 协议之上。那么,这些程序库的使用者就不需要再担心浏览器或者基础架构的限制问题。

Erlang 之父 Joe Armstrong 认为,WebSockets 将会干掉 Comet

在经过了一些试验以后,我可以让 Erlang 和一张 Web 网页通过纯异步消息传输进行通信。

我认为这意味着如下的技术将会消亡:

  • comet
  • 长轮询
  • AJAX
  • 连接保持 socket

其实核心的问题便是,Web 浏览器不能像其他应用一样,打开一个 socket 并进行异步 I/O。而上述技术只不过是些带有各种问题的权宜之计而已。

JettyBayeux协议的创作者之一 Greg Wilkins 认为,WebSocket 规范还不够好,无论是规范的定义,还是连接中断时的行为,都需要改进和完善。其中一个关键问题,便出在协议的表述上:

然而这类规范的一个更实际问题便是规范写的很让人费解,比如:

使得 /b_v/ 为整数,并且为 /b/ 的低 7 位(这个值应由 /b/ 和 0x7F 进行 _and_ 位运算求得)。

将 /length/ 乘以 128,再加上 /b_v/,并将结果储存在 /length/ 中。

如果 /b/ 的高序位被置(例如,/b/ 和 0x80 进行 _and_ 位运算得到 0x80),那么返回到标记为 _length_ 的前一步骤。

我质疑到读者能否确认客户端的数据帧和服务器端的数据帧是对称的,并且由相同的数据帧实现。

相比较这些诘屈聱牙的论述,IETF 规范通常使用精确的扩展巴科斯范式(ABNF,RFC5234)来描述协议,这样便不会引起误读或者混乱。为了能更清楚的说明,我将 4.2 节翻译成了 BNF …

值得注意的是,Jetty 现在既支持 WebSocket又支持 Bayeux。本着调查研究的精神,Greg 写了一篇博客,通过 WebSocket 实现了聊天功能。然而开发过程却并不顺利:

聊天室的典型用例是这样的,你进入了一个聊天室,标识出你的存在,这个标识会一直保持到你显式地退出聊天室。而在 Web 聊天的情况下,你可以收发聊天消息,直到你关闭浏览器或者转至其他网页。不幸的是,即便是这么简单的用例都无法通过 WebSocket 实现,因为在该协议中,连接有一个闲置超时。

为了保持存在的状态,聊天应用要发送“连接保持(Keep Alive)”的消息给 WebSocket,来避免该连接因为闲置超时而关闭。然而,应用并不知道这个闲置超时究竟是多少,因此它只能随便选一个间隔周期(比如 30 秒)来发送该消息,这和长轮询要做的事似乎就多少有些类似了。

通过 onClose 处理、连接保持、消息队列、超时和重试,我们最终实现了一个可以在用户停留在网页上的时候保持其存在状态的聊天室。但是遗憾的是这个聊天室依然还没有完,因为它还需要处理错误和非暂时性故障。

Greg 对 WebSocket 协议的改进给出了一系列的建议,这些建议力图在将来给开发者们带来更多的便捷:

  • WebSocket 将来的版本最好能支持超时发现机制,这样它就可以告诉应用连接保持消息的周期,甚至它能够发连接保持的消息给应用。
  • WebSocket 将来的版本最好能支持有序关闭消息,这样应用就可以分辨出关闭的原因是网络错误(需要将用户的存在状态保持一段时间)还是一次有序关闭,就比如说用户离开了该页面(将用户的存在状态移除)。
  • WebSocket 将来的版本最好能支持顺序关闭,这样没有出错的连接上的递送便可以识别出来,除非服务质量的要求非常高,否则便可以避免复杂的确认。
  • WebSocket 将来的版本最好能支持更详细的连接错误,处理“找不到主机名”和处理“401 未授权”的方式肯定是不一样的。
  • WebSocket 将来的版本最好能支持发送错误状态,比如网络错误或者是闲置超时,这样应用就可以不必对错误进行重试。

最后,他概要性地总结了自己的观点,并谈了谈对未来的期望:

这篇博客说明了 WebSocket 不能解决很多在开发健壮 Comet 网络应用时遇到的复杂问题,也就是说没有银弹。希望在未来版本的 WebSocket 中,可以加入连接保持、超时处理、顺序关闭和错误通知等特性。但是 WebSocket 却不能提供高级的队列处理、超时、重连、重试和退避(Backoff)。如果你想拥有高品质的服务,那么无论是你的应用还是你的框架,都需要支持这些特性才行。

CometD 版本 2 即将发布,支持将 WebSocket 作为替代传输层,来支持现有的 JSON 长轮询和 JSONP 回调轮询。CometD 支持这篇博客所谈到的所有特性,并让它们对浏览器完全透明,无论是否支持 WebSocket 都可以做到。我们真心希望 WebSocket 可以给我们更大的吞吐量和更低的延迟,比现在已经让人印象深刻的长轮询要更好。

Jetty 8.0.0 M0已经发布,提供了对 WebSocket 的支持,同时还支持 Servlet 3.0 API。

查看英文原文:WebSockets and Bayeux/CometD

JavaSOA敏捷.NETRubyWeb框架JavaScript架构云计算DevOps