一、HTTP轮询和JSONP轮询
普通的网页通信,采用的是短轮询,即浏览器定时向服务器发送请求,看有没有更新的数据。Ajax技术的出现,允许脚本发送一个HTTP请求而无需重新加载页面,直到关闭页面,然后重新发起连接。
HTTP轮询
现在需要的是能够通过Web来访问完全动态的应用,这些应用通常需要尽可能的快,提供近乎实时的组件。
对于纯粹的Ajax HTTP请求,为了尽快地获取服务器端事件,轮询的间隔必须尽可能地小。但是,如果间隔减小的话,客户端浏览器就会发出更多的请求,这些请求中的许多都不会返回任何有用的数据,而这将会白白地浪费掉带宽和处理资源。
JSONP 轮询
JSONP 轮询基本上与 HTTP 轮询一样,不同之处在于使用 JSONP 可以发送跨域请求。要在 JavaScript 中实现轮询,可以使用 setInterval
来定期地发出 Ajax 请求。
用 JavaScript 实现的轮询的优缺点:
【优点】:很容易实现,不需要任何服务器端的特定功能,且在所有的浏览器上都能工作。
【缺点】:这种方法很少被用到,因为它是完全不具伸缩性的。试想一下,在 100 个客户端每个都发出 2 秒钟的轮询请求的情况下,所损失的带宽和资源数量,在这种情况下 30% 的请求没有返回数据。
二、Comet:一种更高级的Ajax技术,经常也有人称为“服务器推送”。
Ajax是一种从页面向服务器请求数据的技术,而Comet则是一种从服务器向页面推送数据的技术。Comet能够让信息近乎实时地被推送到页面上,非常适合处理体育比赛的分数和股票报价。
【Comet的优点】:每个客户端始终都有一个向服务器端打开的通信链路,服务器端可以通过在事件到来时立即提交(完成)响应来把事件推给客户端,或者它甚至可以累积再连续发送。因为请求长时间保持打开的状态,故服务器需要特别的功能来特别处理这些长期生存期请求。
有两种实现Comet的方式:长轮询和流。
1. 长轮询
页面发起一个到服务器的请求,然后服务器一直保持连接打开,直到有数据可发送。发送完数据之后,浏览器关闭连接,随即又发起一个到服务器的新请求。这一过程在页面打开期间一直持续不断。Web 服务器可以在无需显式请求的情况下向客户端发送数据。
无论是短轮询还是长轮询,浏览器都要在接收数据之前,先发起对服务器的连接。两者最大的区别在于服务器如何发送数据。短轮询是服务器立即发送响应,无论数据是否有效,而长轮询是等待发送响应。
轮询的优势是所有浏览器都支持,可以使用script 标签或是单纯的XHR对象来实现长轮询。
★ script标签
其目标是把 script 标签附加到页面上以让脚本执行。服务器端则会:挂起连接直到有事件发生,接着把脚本内容发送回浏览器,然后重新打开另一个 script 标签来获取下一个事件。【优点】:因为是基于 HTML 标签的,所有这一技术非常容易实现,且可跨域工作。【缺点】:类似于 iframe 技术,错误处理缺失,不能获得连接的状态或是有干涉连接的能力。
☛ 长轮询的优点和缺点:
【优点】:
- 客户端很容易实现良好的错误处理系统和超时管理。这一可靠的技术还允许在与服务器端的连接之间有一个往返,即使连接是非持久的(当一个应用有许多的客户端时,这是一件好事)。
- 所有浏览器都支持。
【缺点】:相比于其他技术来说,不存在什么重要的缺点,像所有我们已经讨论过的技术一样,该方法依然依赖于无状态的 HTTP 连接,其要求服务器端有特殊的功能来临时挂起连接。
2. 流
流不同于轮询,因为它在页面的整个生命周期内只使用一个HTTP连接。具体来说,就是浏览器向服务器发送一个请求,而服务器保持连接打开,然后周期性地向浏览器发送数据。因为每个到达服务器端的事件都会通过这同一连接来发送,因此,客户端需要有一种方法来把通过这同一连接发送过来的不同响应分隔开来。
从技术上来讲,两种常见的流技术包括 Forever Iframe(或者 hidden IFrame),或是被用来在 JavaScript 中创建 Ajax 请求的 XMLHttpRequest 对象的多部分 (multi-part) 特性。
(1)Forever Frame
Forever Iframe(永存的 Iframe)技术涉及了一个置于页面中的隐藏 Iframe 标签,该标签的 src 属性指向返回服务器端事件的 servlet 路径。每次在事件到达时,servlet 写入并刷新一个新的 script 标签,该标签内部带有 JavaScript 代码,iframe 的内容被附加上这一 script 标签,标签中的内容就会得到执行。
【优点】:实现简单,在所有支持iframe的浏览器上都可用。
【缺点】:
- 占用内存;
- 浏览器必须支持iFrame(web浏览器);
- 没有方法可用来实现可靠的错误处理或是跟踪连接的状态,因为所有的连接和数据都是由浏览器通过 HTML 标签来处理的,因此没有办法知道连接何时在哪一端已被断开了。
- 单向通信(服务器—>客户端),如果客户端要发请求的话,必须重新开一个连接,会占用更多服务器资源,因此,对于聊天室不是一个很好的选择。
(2)multi-part XMLHttpRequest:更加可靠
是在 XMLHttpRequest 对象上使用某些浏览器(比如 Firefox)支持的 multi-part 标志。Ajax 请求被发送给服务器端并保持打开状态,每次有事件到来时,一个多部分的响应就会通过这同一连接来写入。
在服务器端,事情要稍加复杂一些。首先必须要设置多部分请求,然后挂起连接。
【优点】:只打开了一个持久连接,这就是节省了大部分带宽使用率的 Comet 技术。
【缺点】:并非所有的浏览器都支持 multi-part 标志。某些被广泛使用的库,比如说用 Java 实现的 CometD,被报告在缓冲方面有问题。例如,一些数据块(多个部分)可能被缓冲,然后只有在连接完成或是缓冲区已满时才被发送,而这有可能会带来比预期要高的延迟。
三、Event Soucrce/SSE(Server-Sent Event,服务器发送事件)
SSE是围绕只读Comet交互推出的API或者模式。SSE API 用于创建到服务器的单向连接,服务器通过这个连接可以发送任意数量的数据。服务器响应的MIME类型必须是 text/event-stream,而且是浏览器中的JavaScript API能解析格式输出。SSE支持短轮询、长轮询和HTTP流,而且能在断开时自动确定何时重新连接。
【优点】:服务器端相对于WebSockets实现比较简洁,不需要添加任何新组件,用任何后端语言和框架就能继续使用。
【缺点】:不同于 WebSockets 客户端与服务器的双向交流,SSE 是单向连接,一般通过一个独立的Ajax请求从客户端向服务端传送数据,因此会相应增加开销。
在浏览器支持率方面,目前IE浏览器不支持SSE(SSE浏览器支持情况)。需要组合XHR才能实现双向通信,可用于一些订阅推送服务。
四、Web Sockets
Web Sockets的目标是在一个单独的持久连接上提供全双工、双向通信。在JavaScript中创建了Web Socket之后,会有一个HTTP请求发送到浏览器发起链接。在取得服务器响应后,建立的连接会使用HTTP升级从HTTP协议交换为Web Socket协议。也就是说,使用标准的HTTP服务器无法实现Web Sockets,只有支持这种协议的专门服务器才能正常工作。
可以发送和接收任何类型的数据。WebSockets 可被看作是 TCP 套接字,因此由客户端和服务器决定要发送的数据类型。
【优势】:
- WebSockets 提供强大的、双向、低延迟和易于处理的错误。
- 与 Comet 相比,它的 API 易于直接使用,无需使用任何其他层,Comet 需要一个很好的库来处理连接、超时、Ajax 请求、确认以及不同的传输(Ajax 长轮询和 jsonp 轮询)。
- 除最初建立连接时需要借助于现有的HTTP协议,其他时候直接基于TCP完成通信,是浏览器中最通用、最灵活的一个传输机制。
- 带宽消耗上,WebSockets 明显优于长轮询。
【缺点】:
- 它是来自 HTML5 的新规范,并不是所有浏览器都支持它:WebSockets浏览器支持情况
- 在提供自定义数据交换协议同时,也不再享有在一些本由浏览器提供的服务和优化,如状态管理、压缩、缓存等。
五、Flash Sockets
对于不支持 WebSockets 的浏览器,除了Comet,通过Flash的API也是可以实现Socket的。AdobeFlash 通过自己的 Socket 实现完成数据交换,再利用 Flash 暴露出相应的接口为 JavaScript 调用(这些库通常提供相同的官方 WebSocket API),从而达到实时传输目的。此方式比轮询要高效,但是随着Flash正式宣告退出历史舞台,这种方式现在的存在意义不大。
优势:FlashSockets 透明地提供 WebSockets 功能,即使在不支持 HTML5 WebSockets 的浏览器上也是如此。
缺点:
- 它需要安装 Flash 插件(通常所有浏览器都有该插件)。
- 它要求打开防火墙的 843 端口,以便 Flash 组件能够执行 HTTP 请求来检索包含域授权的策略文件。
如果无法访问 843 端口,那么库应回退或给出一个错误。所有处理都需要一定的时间(最多 3 秒钟,具体取决于库),这会减慢网站速度。 - 如果客户端在代理服务器后面,那么到 843 端口的连接可能遭到拒绝。
✎ 建议
因为所有现代的浏览器都支持跨域资源共享(Cross-Origin Resource Share,CORS)规范,该规范允许XHR执行跨域请求,因此基于脚本的和基于 iframe 的技术已成为了一种过时的需要。
为反向 Ajax 实现和使用 Comet 的最好方法是通过 XMLHttpRequest 对象,它提供了一个真正的连接句柄和错误处理。考虑到不是所有的浏览器都支持 multi-part 标志,且多部分流可能会遇到缓冲问题,因此建议选择经由 HTTP 长轮询使用 XMLHttpRequest 对象(在服务器端挂起的一个简单的 Ajax 请求)的 Comet 模式,所有支持 Ajax 的浏览器也都支持该种做法。
一般来说,如果想要在低延迟通信、超时和错误检测、简易性,以及所有浏览器和平台的良好支持这几方面有一个最好的折中的话,那就选择使用了 Ajax 长轮询请求的 Comet。
Comet vs WebSockets:
与 Comet 相比,WebSockets 带来很多好处。在日常开发过程中,支持 WebSockets 的客户端的速度变得更快,产生的需求也更少(因此,使用的带宽也更少)。但是,由于并非所有浏览器都支持 WebSockets,因此支持反向 Ajax 库的最佳选择将是能够检测到 WebSockets 支持,如果不支持 WebSockets,则回退到 Comet(长轮询)。
由于需要这两种技术来最大程度地利用所有浏览器并保持兼容性,因此建议使用在这些技术之上提供抽象层的客户端 JavaScript 库。
✔ Socket.io
- 一个100%由 JavaScript 实现、基于Node.js的用于实时通信、跨平台的开源框架,它包括了客户端的 JavaScript 和服务器端的 Node.js 实现。
- 提供了一个与 WebSocket 类似的 API,用该 API 连接到远程服务器,以便异步发送和接收消息。
- 能够在不同版本的浏览器(包括移动设备浏览器在内)间实现实时通信,能够检测浏览器功能并尝试选择可用的最佳传输,从性能最好的到性能最糟糕的自动降级。
- 还提供了心跳、超时、断开连接和错误处理等功能。
- 实现了对于其他服务端语言的支持,如Java、C++、Swift。
通过提供通用 API,该库支持多种传输(在下列方案中自动降级):
- WebSocket
- Adobe@ Flash@ Socket
- AJAX long polling
- AJAX multipart streaming
- Forever Iframe
- JSONP Polling
浏览器兼容情况:
结束语
所有 Web 容器都支持 Comet,并且几乎都支持 WebSocket。即使规范会导致几种不同的本机实现 ,仍然可以使用 Comet 和 服务端语言的 API 来开发 Web 应用程序。甚至,还可以使用诸如 Socket.IO 之类的库来透明地使用 Comet 和 WebSocket。
用于 Java 服务器的最有名的开源反向 Ajax 库有Atmosphere 和 CometD,这两个库和Socket.io一样,提供了多浏览器支持、美妙的用户体验,以及错误处理、更易于使用的 API、超时以及重新连接带来的好处。更多内容请查看反向 Ajax,第 4 部分Atmosphere 和 CometD。