为什么实时 web 这么重要?我们生活在一个实时(real-time)的世界中,因此 Web 的最终最自然的状态也应当是实时的。用户需要实时的沟通、数据和搜索。我们对互联网信息实时性的要求也越来越高,如果信息或消息延时几分钟后才更新,简直让人无法忍受。现在很多大公司(如 Google、Facebook 和 Twitter)已经开始关注实时 Web,并提供了实时性服务。实时 Web 将是未来最热门的话题之一。
传统的 Web 是基于 HTTP 的请求 / 响应模型的:客户端请求一个新页面,服务器将内容发送到客户端,客户端再请求另外一个页面时又要重新发送请求。后来有人提出了 AJAX,AJAX 使得页面的体验更加 "动态",可以在后台发起到服务器的请求。但是,如果服务器有更多数据需要推送到客户端,在页面加载完成后是无法实现直接将数据从服务器发送给客户端的。实时数据无法被 "推送" 给客户端。
为了解决这个问题,有人提出了很多解决方案。最简单(暴力)的方案是用轮询:每隔一段时间都会向服务器请求新数据。这让用户感觉应用是实时的。实际上这会造成延时和性能问题,因为服务器每秒都要处理大量的连接请求,每次请求都会有 TCP 三次握手并附带 HTTP 的头信息。尽管现在很多应用仍在使用轮询,但这并不是最理想的解决方案。
后来随着 Comet 技术的提出,又出现了很多更高级的解决方案。这些技术方案包括永久帧(forever frame)、XHR 流(xhr-multipart)、htmlfile,以及长轮询。长轮询是指,客户端发起一个到服务器的 XHR 连接,这个连接永不关闭,对客户端来说连接始终是挂起状态。当服务器有新数据时,就会及时地将响应发送给客户端,接着再将连接关闭。然后重复整个过程,通过这种方式就实现了 "服务器推"(server push)。
Comet 技术是非标准的 hack 技术,正因为此,浏览器端的兼容性就成了问题。首先,性能问题无法解决,向服务器发起的每个连接都带有完整的 HTTP 头信息,如果你的应用需要很低的延时,这将是一个棘手的问题。当然不是说 Comet 本身有问题,因为还没有其他替代方案前 Comet 是我们的唯一选择。
浏览器插件(如 Flash)和 Java 同样被用于实现服务器推。它们可以基于 TCP 直接和服务器建立 socket 连接,这种连接非常适合将实时数据推给客户端。问题是并不是所有的浏览器都安装了这些插件,而且它们常常被防火墙拦截,特别是在公司网络中。
现在 HTML5 规范为我们准备了一个替代方案。但这个规范稍微有些超前,很多浏览器都还不支持,特别是 IE,对于现在很多开发者来说帮助不大,鉴于大部分浏览器还未实现 HTML5 的 WebSocket,现行最好的办法仍然是使用 Comet。
WebSocket(
和之前的服务器推的技术相比,WebSocket 有着巨大的优势,因为 WebSocket 是全双工的,而不是基于 HTTP 的,一旦建立连接就不会断掉。Comet 所面对的现实问题就是 HTTP 的体积太大,每个请求都带有完整的 HTTP 头信息。而且包含很多没有用的 TCP 握手,因为 HTTP 是比 TCP 更高层次的网络协议。
使用 WebSocket 时,一旦服务器和客户端之间完成握手,信息就可以畅通无阻地随意往来于两端,而不用附加那些无用的 HTTP 头信息。这极大地降低了带宽的占用,提高了性能。因为连接一直处于活动状态,服务器一旦有新数据要更新时就可以立即发送给客户端(不需要客户端先请求,服务器再响应了)。另外,连接是双工的,因此客户端同样可以发送数据给服务器,当然也不需要附带多余的 HTTP 头。
下面这段话出自 Google 的 Ian Hickson,HTML5 规范小组负责人,它是这样描述 WebSocket 的:
现在我们来看一下都有哪些浏览器支持 WebSocket:
- Chrome >= 4
- Safari >= 5
- iOS >= 4.2
- Firefox >= 4*
- Opera >= 11*
尽管 Firefox 和 Opera 也都实现了 WebSocket,但考虑到 WebSocket 仍然存在安全隐患,默认并没有启用它。但这不是什么大问题,或许本书出版时 WebSocket 的安全问题就已经解决了。同时你也可以在那些对 WebSocket 支持不好的浏览器中进行降级处理,使用诸如 Comet 和 Flash 的笨方法。
检测浏览器是否支持 WebSocket 也非常简单、直接:
- varsupported=("WebSocket"inwindow);
- if(supported)alert("WebSocketsaresupported");
长远来看,浏览器的 WebSocket API 非常清晰且合乎逻辑。可以使用 WebSocket 类来实例化一个新的套接字(socket),这需要传入服务器的端地址,在这个例子中是 ws://example.com:
- var socket = new WebSocket("ws://example.com");
然后我们需要给这个套接字添加事件监听 :
- // 建立连接
- socket.onopen = function() {
- /* ... */
- }
- // 通过连接发送了一些新数据
- socket.onmessage = function(data) {
- /* ... */
- }
- // 关闭连接
- socket.onclose = function() {
- /* ... */
- }
当服务器发送一些数据时,就会触发 onmessage 事件,同样,客户端也可以调用 send() 函数将数据传回服务器。很明显,我们应当在连接建立且触发了 onopen 事件之后调用它:
- socket.onmessage=function(msg){
- console.log("Newdata-",msg);
- };
- socket.onopen=function(){
- socket.send("Why,hellothere").
- };
- 发送和接收的消息只支持字符串格式。但在字符串和JSON数据之间可以很轻松地相互转换,这样就可以创建你自己的协议:
- varrpc={
- test:function(arg1,arg2){/*...*/}
- };
- socket.onmessage=function(data){
- //解析JSON
- varmsg=JSON.parse(data);
- //调用RPC函数
- rpc[msg.method].apply(rpc,msg.args);
- };
这段代码中,我们创建了一个远程过程调用(remoteprocedurecall,RPC)脚本,服务器可以发送一些简单的 JSON 来调用客户端的函数,就像下面这行代码:
- {
- "method": "test",
- "args": [1, 2]
- }
注意,这里的调用是限制在 rpc 对象里的。这样做的原因主要是出于安全考虑,如果允许在客户端执行任意 JavaScript 代码,黑客就会利用这个漏洞。可以调用 close() 函数来关闭这个连接:
- varsocket=newWebSocket("ws://localhost:8000/server");
你肯定注意到了我们在实例化一个 WebSocket 的时候使用了 WebSocket 特有的协议前缀 ws://,而不是 http://。WebSocket 同样支持加密的连接,这需要使用以 wss:// 为协议前缀的 TLS。默认情况下 WebSocket 使用 80 端口建立非加密的连接,使用 443 端口建立加密的连接。你可以通过给 URL 带上自定义端口来覆盖默认配置。要记住,并不是所有的端口都可以被客户端使用,一些非常规的端口很容易被防火墙拦截。
说到现在,你或许会想,"我还不能在项目中使用 WebSocket,因为标准还未成型,而且 IE 不支持 WebSocket"。这样的想法并没有错,幸运的是,我们有解决方案。Web-socket-js 是一个基于 AdobeFlash 实现的 WebSocket。用这个库就可以在不支持 WebSocket 的浏览器中做优雅降级。毕竟几乎所有的浏览器都安装了 Flash 插件。基于 Flash 实现的 SocketAPI 和 HTML5 标准规范完全一样,因此当 WebSocket 的浏览器兼容性更好的时候,只需简单地将库移除即可,而不必对代码做任何修改。
尽管客户端的 API 非常简洁、直接,但在服务器端情况就不同了。WebSocket 协议包含两个互不兼容的草案协议:草案 75 和草案 76。服务器需要通过检测客户端使用的连接握手类型来判断使用哪个草案协议。
WebSocket 首先向服务器发起一个 HTTP"升级"(upgrade)请求。如果你的服务器支持 WebSocket,则会执行 WebSocket 握手并初始化一个连接。"升级" 请求中包含了原始域(请求所发出的域名)的信息。客户端可以和任意域名建立 WebSocket 连接,只有服务器才会决定哪些客户端可以和它建立连接,常用做法是将允许连接的域名做成白名单。
在 WebSocket 的设计之初,设计者们希望只要初始连接使用了常用的端口和 HTTP 头字段,就可以和防火墙和代理软件和谐相处。然而理想是丰满的,现实是骨感的。有些代理软件对 WebSocket 的 "升级" 请求的头信息做了修改,打破了协议规则。事实上,协议草案的最近一次更新(版本 76)也无意中打破了对反向代理和网关的兼容性。为了更好更成功地使用 WebSocket,这里给出一些步骤:
那么,如何选择服务器端的解决方案呢?幸运的是,在很多语言中都实现了对 WebSocket 的支持,比如 Ruby、Python 和 Java。要再次确认每个实现是否支持最新的 76 版协议草案,因为这个协议是被大多数客户端所支持的。
─node-Websocket-server(
─Socket.IO(
─EventMachine(
─Cramp(
─Sunshowers(
─Twisted(
─Apachemodule(
─php-Websocket(
─Jetty(
─native(
来源: