请选择 进入手机版 | 继续访问电脑版

默认
打赏 发表评论 0
Web端即时通讯技术的发展与WebSocket、Socket.io的技术实践

前言


为什么说Web即时通讯技术这么重要?我们生活在一个实时(real-time)的世界中,因此Web的最终最自然的状态也应当是实时的。用户需要实时的沟通、数据和搜索。我们对互联网信息实时性的要求也越来越高,如果信息或消息延时几分钟后才更新,简直让人无法忍受。现在很多大公司(如Google、Facebook和Twitter)都在关注实时Web,并提供了实时性服务。实时Web是现在也将是未来最热门的话题之一。

相关技术文章


Web端即时通讯新手入门贴:
新手入门贴:详解Web端即时通讯技术的原理

关于Ajax短轮询:
找这方面的资料没什么意义,除非忽悠客户,否则请考虑其它3种方案即可。

有关Comet技术的详细介绍请参见:
Comet技术详解:基于HTTP长连接的Web端实时通信技术
WEB端即时通讯:HTTP长连接、长轮询(long polling)详解
WEB端即时通讯:不用WebSocket也一样能搞定消息的即时性
开源Comet服务器iComet:支持百万并发的Web端即时通讯方案

有关WebSocket的详细介绍请参见:
WebSocket详解(一):初步认识WebSocket技术
WebSocket详解(二):技术原理、代码演示和应用案例
WebSocket详解(三):深入WebSocket通信协议细节
Web端即时通讯技术的发展与WebSocket、Socet.io的技术实践
Socket.IO介绍:支持WebSocket、用于WEB端的即时通讯的框架
socket.io和websocket 之间是什么关系?有什么区别?

有关SSE的详细介绍文章请参见:
SSE技术详解:一种全新的HTML5服务器推送事件技术

更多WEB端即时通讯文章请见:
http://www.52im.net/forum.php?mod=collection&action=view&ctid=15

Web端即时通讯的发展历史


传统的Web是基于HTTP的请求/响应模型的:即客户端请求一个新页面,服务器将内容发送到客户端,客户端再请求另外一个页面时又要重新发送请求。

后来有人提出了AJAX,AJAX使得页面的体验更加“动态”,可以在后台发起到服务器的请求。但是,如果服务器有更多数据需要推送到客户端,在页面加载完成后是无法实现直接将数据从服务器发送给客户端的。实时数据无法被“推送”给客户端。

为了解决这个问题,有人提出了很多解决方案。最简单(暴力)的方案是用轮询:每隔一段时间都会向服务器请求新数据。这让用户感觉应用是实时的,实际上这会造成延时和性能问题,因为服务器每秒都要处理大量的连接请求,每次请求都会有TCP三次握手并附带HTTP的头信息。尽管现在很多应用仍在使用轮询,但这并不是最理想的解决方案。关于轮询技术详解可见文章《WEB端即时通讯:HTTP长连接、长轮询(long polling)详解》。

后来随着Comet技术的提出,又出现了很多更高级的解决方案。这些技术方案包括永久帧(forever frame)、XHR流(xhr-multipart)、htmlfile,以及长轮询。长轮询是指,客
户端发起一个到服务器的XHR连接,这个连接永不关闭,对客户端来说连接始终是挂起状态。当服务器有新数据时,就会及时地将响应发送给客户端,接着再将连接关闭。然后重复整个过程,通过这种方式就实现了“服务器推”(server push)。关于Comet技术详解可见文章《Comet技术详解:基于HTTP长连接的Web端实时通信技术》。

Comet技术是非标准的hack技术,正因为此,浏览器端的兼容性就成了问题。首先,性能问题无法解决,向服务器发起的每个连接都带有完整的HTTP头信息,如果你的应用需要很低的延时,这将是一个棘手的问题。当然不是说Comet本身有问题,因为还没有其他替代方案前Comet是我们的唯一选择。

浏览器插件(如Flash)和Java同样被用于实现服务器推。它们可以基于TCP直接和服务器建立socket连接,这种连接非常适合将实时数据推给客户端。问题是并不是所有的浏览器都安装了这些插件,而且它们常常被防火墙拦截,特别是在公司网络中。

现在HTML5规范为我们准备了一个替代方案。但这个规范稍微有些超前,暂时有部分或较近老的浏览器都还不支持,特别是IE,但对于现在很多开发者来说,HTML5的WebSocket必定会是未来的潮流和趋势,值得深入研究和了解。关于WebSocket详解文章请参见《WebSocket详解(一):初步认识WebSocket技术》、《WebSocket详解(二):技术原理、代码演示和应用案例》、《WebSocket详解(三):深入WebSocket通信协议细节》。

WebSocket 技术实践


WebSocket(http://dev.w3.org/html5/websockets)是HTML5规范(http://www.w3.org/TR/html5)的一部分,提供了基于TCP的双向的、全双工的socket连接。这意味着服务器可以直接将数据推送给客户端,而不需要开发者求助于长轮询或插件来实现,这是一个很大的进步。尽管有一些浏览器无法支持WebSocket,对于那些不支持WebSocket的浏览器,可以降级使用笨方法来实现,比如Comet或轮询。

和之前的服务器推的技术相比,WebSocket有着巨大的优势,因为WebSocket是全双工的,而不是基于HTTP的,一旦建立连接就不会断掉。Comet所面对的现实问题就是HTTP的体积太大,每个请求都带有完整的HTTP头信息。而且包含很多没有用的TCP握手,因为HTTP是比TCP更高层次的网络协议。

使用WebSocket时,一旦服务器和客户端之间完成握手,信息就可以畅通无阻地随意往来于两端,而不用附加那些无用的HTTP头信息。这极大地降低了带宽的占用,提高了性能。因为连接一直处于活动状态,服务器一旦有新数据要更新时就可以立即发送给客户端(不需要客户端先请求,服务器再响应了)。另外,连接是双工的,因此客户端同样可以发送数据给服务器,当然也不需要附带多余的HTTP头。

下面这段话出自Google的Ian Hickson,HTML5规范小组负责人,它是这样描述WebSocket的:

将千字节的数据降为2字节……并将延时从150毫秒降为50毫秒,这种优化跨越了不止一个量级,实际上仅这两点优化就足以让Google确信WebSocket会给产品带来非一般的用户体验。


现在我们来看一下都有哪些浏览器支持WebSocket:

  • IE >= 10
  • Chrome >= 4
  • Safari >= 5
  • iOS >= 4.2
  • Firefox >= 4*
  • Opera >= 11*

检测浏览器是否支持WebSocket也非常简单、直接:
var supported = ("WebSocket" in window);
if (supported) alert("WebSockets are supported");

长远来看,浏览器的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("New data - ", msg);
};
socket.onopen = function(){
socket.send("Why, hello there").
};

发送和接收的消息只支持字符串格式。但在字符串和JSON数据之间可以很轻松地相互转换,这样就可以创建你自己的协议:
var rpc = {
test: function(arg1, arg2) { /* ... */ }
};
socket.onmessage = function(data){
// 解析 JSON
var msg = JSON.parse(data);
// 调用 RPC 函数
rpc[msg.method].apply(rpc, msg.args);
};

这段代码中,我们创建了一个远程过程调用(remote procedure call,RPC)脚本,服务器可以发送一些简单的JSON来调用客户端的函数,就像下面这行代码:
{"method": "test", "args": [1, 2]}

注意,这里的调用是限制在rpc对象里的。这样做的原因主要是出于安全考虑,如果允许在客户端执行任意JavaScript代码,黑客就会利用这个漏洞。可以调用close()函数来关闭这个连接:
var socket = new WebSocket("ws://localhost:8000/server");

你肯定注意到了我们在实例化一个WebSocket的时候使用了WebSocket特有的协议前缀ws://,而不是http://。WebSocket同样支持加密的连接,这需要使用以wss://为协议前缀的TLS。默认情况下WebSocket使用80端口建立非加密的连接,使用443端口建立加密的连接。你可以通过给URL带上自定义端口来覆盖默认配置。要记住,并不是所有的端口都可以被客户端使用,一些非常规的端口很容易被防火墙拦截。

说到现在,你或许会想,“我还不能在项目中使用WebSocket,因为标准还未成型,而且IE不支持WebSocket”。这样的想法并没有错,幸运的是,我们有解决方案。Web-socket-js(https://github.com/gimite/web-socket-js)是一个基于AdobeFlash实现的WebSocket。用这个库就可以在不支持WebSocket的浏览器中做优雅降级。毕竟几乎所有的浏览器都安装了Flash插件。基于Flash实现的SocketAPI和HTML5标准规范完全一样,因此当WebSocket的浏览器兼容性更好的时候,只需简单地将库移除即可,而不必对代码做任何修改。

尽管客户端的API非常简洁、直接,但在服务器端情况就不同了。WebSocket协议包含两个互不兼容的草案协议:草案75(http://goo.gl/cgSjp)和草案76(http://goo.gl/2u78y)。服务器需要通过检测客户端使用的连接握手类型来判断使用哪个草案协议。

WebSocket首先向服务器发起一个HTTP“升级”(upgrade)请求。如果你的服务器支持WebSocket,则会执行WebSocket握手并初始化一个连接。“升级”请求中包含了原始域(请求所发出的域名)的信息。客户端可以和任意域名建立WebSocket连接,只有服务器才会决定哪些客户端可以和它建立连接,常用做法是将允许连接的域名做成白名单。

在WebSocket的设计之初,设计者们希望只要初始连接使用了常用的端口和HTTP头字段,就可以和防火墙和代理软件和谐相处。然而理想是丰满的,现实是骨感的。有些代理软件对WebSocket的“升级”请求的头信息做了修改,打破了协议规则。事实上,协议草案的最近一次更新(版本76)也无意中打破了对反向代理和网关的兼容性。为了更好更成功地使用WebSocket,这里给出一些步骤:

  • 使用安全的WebSocket连接(wss)。代理软件不会对加密的连接胡乱篡改,此外你所发送的数据都是加密后的,不容易被他人窃取。
  • 在WebSocket服务器前面使用TCP负载均衡器,而不要使用HTTP负载均衡器,除非某个HTTP负载均衡器大肆宣扬自己支持WebSocket。
  • 不要假设浏览器支持WebSocket,虽然浏览器支持WebSocket只是时间问题。诚然,如果连接无法快速建立,则迅速优雅降级使用Comet和轮询的方式来处理。

那么,如何选择服务器端的解决方案呢?幸运的是,在很多语言中都实现了对WebSocket的支持,比如Ruby、Python和Java。要再次确认每个实现是否支持最新的76版协议草案,因为这个协议是被大多数客户端所支持的。


Node.js和Socket.IO 技术实践


在上面的名单中,Node.js(http://nodejs.org)是一名新成员,也是当下最受关注的新技术。Node.js是基于事件驱动的JavaScript服务器,采用了Google的V8引擎(http://code.google.com/p/v8)。正因为此,Node.js速度非常快,也可以解决服务器高并发连接数的资源消耗问题,和WebSocket服务器一样。

Socket.IO(http://socket.io/)是一个Node.js库,实现了WebSocket。最让人感兴趣的不止于此,来看一段官网上的宣传文字:

Socket.IO的目标是在每个浏览器和移动设备中构建实时APP,这缩小了多种传输机制之间的差异。


如果环境支持WebSocket,那么Socket.IO就会尝试使用WebSocket,若有必要也会降级使用其他的传输方式。

这里列出了Socket.IO所支持的传输方式,非常全面,因此WebSocket.IO可以做到更好的浏览器兼容:

  • WebSocket
  • Adobe Flash Socket
  • ActiveX HTMLFile (IE)
  • 基于 multipart 编码发送 XHR(XHR with multipart encoding)
  • 基于长轮询的XHR
  • JSONP 轮询(用于跨域的场景)

Socket.IO 的浏览器支持非常全面。“服务器推”的实现是众所周知的难题,但Socket.IO团队为你解决了这些烦恼,Socket.IO保证了它能兼容大多数浏览器。

Socket.IO浏览器兼容情况如下:

  • Safari >= 4
  • Chrome >= 5
  • IE >= 6
  • iOS
  • Firefox >= 3
  • Opera >= 10.61

尽管在服务器端实现的Socket.IO最初是基于Node.js的,现在也有用其他语言实现的版本了,比如Ruby(Rack)(http://github.com/markjeee/Socket.IQ-rack),Python(Tornado)
https://github.com/MrJoes/tornadio),Java(http://code.google.com/p/socketio-java)和
GoogleGo(http://github.com/madari/go-socket.io)。

来看一下Socket.IO的API,写法非常简单、直接,客户端的API和WebSocket的API看起来很像:
var socket = new io.Socket();
socket.on("connect", function(){
socket.send('hi!');
});
socket.on("message", function(data){
alert(data);
});
socket.on("disconnect", function(){});

在后台Socket.IO会选择使用最佳的传输方式。正如在readme文件中所描述的,“你可以使用Socket.IO在任何地方构建实时APP”。

如果你想寻求比Socket.IO更高级的解决方案,可以关注一下Juggernaut(http://github.com/maccman/juggernaut),它就是基于Socket.IO实现的。Juggernaut包含一个信道接口(channelinterface):客户端可以订阅信道监听,服务器端可以向信道发布消息,即所谓的订阅/发布(http://en.wikipedia.org/wiki/PubSub)模式。这个库可以针对不同的客户端和实现环境作灵活扩展,比如基于TLS等。

如果你需要虚拟主机中的解决方案,可以参考Pusher(http://pusherapp.com/)。Pusher可以让你从繁杂的服务器管理事务中抽身出来,使你能将注意力集中在有意义的部分:Web应用的开发。客户端的实现非常简单,只需将JavaScript文件引入页面中并订阅信道监听即可。当有消息发布的时候,仅仅是发送一个HTTP请求到RESTAPI(http://pusherapp.com/docs)。

(原文链接:http://www.infoq.com/cn/articles/JavaScript-Web,有删节和改动)

即时通讯网 - 即时通讯开发者社区! 来源: - 即时通讯开发者社区!

本帖已收录至以下技术专辑

推荐方案
打赏楼主 ×
使用微信打赏! 使用支付宝打赏!

Processed in 0.140625 second(s), 35 queries , Gzip On.

返回顶部