标签:
有关IM(InstantMessaging)聊天应用(如:微信,QQ)、消息推送技术(如:现今移动端APP标配的消息推送模块)等即时通讯应用场景下,大多数都是桌面应用程序或者native应用较为流行,而网上关于原生IM(相关文章请参见:《IM架构篇》、《IM综合资料》、《IM/推送的通信格式、协议篇》、《IM心跳保活篇》、《IM安全篇》、《实时音视频开发》)、消息推送应用(参见:《推送技术好文》)的通信原理介绍也较多,此处不再赘述。
而web端的IM应用,由于浏览器的兼容性以及其固有的“客户端请求服务器处理并响应”的通信模型,造成了要在浏览器中实现一个兼容性较好的IM应用,其通信过程必然是诸多技术的组合,本文的目的就是要详细探讨这些技术并分析其原理和过程。
- 更多即时通讯技术资料:http://www.52im.net/forum.php?mod=collection&op=all
- 即时通讯开发交流群:215891622
【Web端即时通讯技术盘点请参见】:
《Web端即时通讯技术盘点:短轮询、Comet、Websocket、SSE》
【关于Ajax短轮询】:
找这方面的资料没什么意义,除非忽悠客户,否则请考虑其它3种方案即可。
【有关Comet技术的详细介绍请参见】:
《Comet技术详解:基于HTTP长连接的Web端实时通信技术》
《WEB端即时通讯:HTTP长连接、长轮询(long polling)详解》
《WEB端即时通讯:不用WebSocket也一样能搞定消息的即时性》
《开源Comet服务器iComet:支持百万并发的Web端即时通讯方案》
【有关WebSocket的详细介绍请参见】:
《WebSocket详解(一):初步认识WebSocket技术》
《WebSocket详解(二):技术原理、代码演示和应用案例》
《WebSocket详解(三):深入WebSocket通信协议细节》
《Socket.IO介绍:支持WebSocket、用于WEB端的即时通讯的框架》
《socket.io和websocket 之间是什么关系?有什么区别?》
【有关SSE的详细介绍文章请参见】:
【更多WEB端即时通讯文章请见】:
http://www.52im.net/forum.php?mod=collection&action=view&ctid=15
浏览器本身作为一个瘦客户端,不具备直接通过系统调用来达到和处于异地的另外一个客户端浏览器通信的功能。这和我们桌面应用的工作方式是不同的,通常桌面应用通过socket可以和远程主机上另外一端的一个进程建立TCP连接,从而达到全双工的即时通信。
浏览器从诞生开始一直走的是客户端请求服务器,服务器返回结果的模式,即使发展至今仍然没有任何改变。所以可以肯定的是,要想实现两个客户端的通信,必然要通过服务器进行信息的转发。例如A要和B通信,则应该是A先把信息发送给IM应用服务器,服务器根据A信息中携带的接收者将它再转发给B,同样B到A也是这种模式,如下所示:
我们认识到基于web实现IM软件依然要走浏览器请求服务器的模式,这这种方式下,针对IM软件的开发需要解决如下三个问题:
双全工通信:
即达到浏览器拉取(pull)服务器数据,服务器推送(push)数据到浏览器;
低延迟:
即浏览器A发送给B的信息经过服务器要快速转发给B,同理B的信息也要快速交给A,实际上就是要求任何浏览器能够快速请求服务器的数据,服务器能够快速推送数据到浏览器;
支持跨域:
通常客户端浏览器和服务器都是处于网络的不同位置,浏览器本身不允许通过脚本直接访问不同域名下的服务器,即使IP地址相同域名不同也不行,域名相同端口不同也不行,这方面主要是为了安全考虑。
即时通讯网注:关于浏览器跨域访问导致的安全问题,有一个被称为CSRF网络攻击方式,请看下面的摘录
CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。
你这可以这么理解CSRF攻击:攻击者盗用了你的身份,以你的名义发送恶意请求。CSRF能够做的事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账......造成的问题包括:个人隐私泄露以及财产安全。
CSRF这种攻击方式在2000年已经被国外的安全人员提出,但在国内,直到06年才开始被关注,08年,国内外的多个大型社区和交互网站分别爆出CSRF漏洞,如:NYTimes.com(纽约时报)、Metafilter(一个大型的BLOG网站),YouTube和百度HI......而现在,互联网上的许多站点仍对此毫无防备,以至于安全业界称CSRF为“沉睡的巨人”。
基于以上分析,下面针对这三个问题给出解决方案。
这是最简单的一种解决方案,其原理是在客户端通过Ajax的方式的方式每隔一小段时间就发送一个请求到服务器,服务器返回最新数据,然后客户端根据获得的数据来更新界面,这样就间接实现了即时通信。优点是简单,缺点是对服务器压力较大,浪费带宽流量(通常情况下数据都是没有发生改变的)。
客户端代码如下:
(简书无法支持程序代码样式,详细代码请见同步发布文章:http://www.52im.net/thread-338-1-1.html)
创建一个XHR对象,每2秒就请求服务器一次获取服务器时间并打印出来。
服务端代码(Node.js):
(简书无法支持程序代码样式,详细代码请见同步发布文章:http://www.52im.net/thread-338-1-1.html)
结果如下:
在上面的轮询解决方案中,由于每次都要发送一个请求,服务端不管数据是否发生变化都发送数据,请求完成后连接关闭。这中间经过的很多通信是不必要的,于是又出现了长轮询(long-polling)方式。这种方式是客户端发送一个请求到服务器,服务器查看客户端请求的数据是否发生了变化(是否有最新数据),如果发生变化则立即响应返回,否则保持这个连接并定期检查最新数据,直到发生了数据更新或连接超时。同时客户端连接一旦断开,则再次发出请求,这样在相同时间内大大减少了客户端请求服务器的次数。代码如下。(详细技术文章请参见《WEB端即时通讯:HTTP长连接、长轮询(long polling)详解》)
客户端:
(简书无法支持程序代码样式,详细代码请见同步发布文章:http://www.52im.net/thread-338-1-1.html)
在XHR对象的readySate为4的时候,表示服务器已经返回数据,本次连接已断开,再次请求服务器建立连接。
服务端代码:
(简书无法支持程序代码样式,详细代码请见同步发布文章:http://www.52im.net/thread-338-1-1.html)
在服务端通过生成一个在1到9之间的随机数来模拟判断数据是否发生了变化,当随机数在0到5之间表示数据发生了变化,直接返回,否则保持连接,每隔2秒再检测。
结果如下:
可以看到返回的时间是没有规律的,并且单位时间内返回的响应数相比polling方式较少。
解决方案3.3:基于http-stream通信
(简书无法支持程序代码样式,详细代码请见同步发布文章:http://www.52im.net/thread-338-1-1.html)
服务端定时发送随机数给客户端,并调用客户端process函数。
在IE5中测试结果如下:
可以看到实现在低版本IE中客户端到服务器的请求-推送的即时通信。
3.3.3基于htmlfile的数据流通信
又出现新问题了,在IE中,使用iframe请求服务端,服务端保持通信连接没有全部返回之前,浏览器title一直处于加载状态,并且底部也显示正在加载,这对于一个产品来讲用户体验是不好的,于是谷歌的天才们又想出了一中hack方式。就是在IE中,动态生成一个htmlfile对象,这个对象ActiveX形式的com组件,它实际上就是一个在内存中实现的HTML文档,通过将生成的iframe添加到这个内存中的HTMLfile中,并利用iframe的数据流通信方式达到上面的效果。同时由于HTMLfile对象并不是直接添加到页面上的,所以并没有造成浏览器显示正在加载的现象。代码如下。
客户端:
(简书无法支持程序代码样式,详细代码请见同步发布文章:http://www.52im.net/thread-338-1-1.html)
服务端传送给iframe的是这样子:
(简书无法支持程序代码样式,详细代码请见同步发布文章:http://www.52im.net/thread-338-1-1.html)
注意这里服务端输出的数据content-type首部要设定为application/javascript,否则某些浏览器会将其当做文本解析。
结果如下:
在上面的这些解决方案中,都是利用浏览器单向请求服务器或者服务器单向推送数据到浏览器这些技术组合在一起而形成的hack技术,在HTML5中,为了加强web的功能,提供了websocket技术,它不仅是一种web通信方式,也是一种应用层协议。它提供了浏览器和服务器之间原生的双全工跨域通信,通过浏览器和服务器之间建立websocket连接(实际上是TCP连接),在同一时刻能够实现客户端到服务器和服务器到客户端的数据发送。关于该技术的原理,请参见:《WebSocket详解(一):初步认识WebSocket技术》、《WebSocket详解(二):技术原理、代码演示和应用案例》、《WebSocket详解(三):深入WebSocket通信协议细节》,此处就不在赘述了,直接给出代码。在看代码之前,需要先了解websocket整个工作过程。
首先是客户端new 一个websocket对象,该对象会发送一个http请求到服务端,服务端发现这是个webscoket请求,会同意协议转换,发送回客户端一个101状态码的response,以上过程称之为一次握手,经过这次握手之后,客户端就和服务端建立了一条TCP连接,在该连接上,服务端和客户端就可以进行双向通信了。这时的双向通信在应用层走的就是ws或者wss协议了,和http就没有关系了。所谓的ws协议,就是要求客户端和服务端遵循某种格式发送数据报文(帧),然后对方才能够理解。
关于ws协议要求的数据格式官网指定如下:
其中比较重要的是FIN字段,它占用1位,表示这是一个数据帧的结束标志,同时也下一个数据帧的开始标志。opcode字段,它占用4位,当为1时,表示传递的是text帧,2表示二进制数据帧,8表示需要结束此次通信(就是客户端或者服务端哪个发送给对方这个字段,就表示对方要关闭连接了)。9表示发送的是一个ping数据。mask占用1位,为1表示masking-key字段可用,masking-key字段是用来对客户端发送来的数据做unmask操作的。它占用0到4个字节。Payload字段表示实际发送的数据,可以是字符数据也可以是二进制数据。
所以不管是客户端和服务端向对方发送消息,都必须将数据组装成上面的帧格式来发送。
首先来看服务端代码:
(简书无法支持程序代码样式,详细代码请见同步发布文章:http://www.52im.net/thread-338-1-1.html)
服务端通过监听data事件来获取客户端发送来的数据,如果是握手请求,则发送http 101响应,否则解析得到的数据并打印出来,然后判断是不是断开连接的请求(Opcode为8),如果是则断开连接,否则将接收到的数据组装成帧再发送给客户端。
客户端代码:
(简书无法支持程序代码样式,详细代码请见同步发布文章:http://www.52im.net/thread-338-1-1.html)
客户端创建一个websocket对象,在onopen时间触发之后(握手成功后),给页面上的button指定一个事件,用来发送页面input当中的信息,服务端接收到信息打印出来,并组装成帧返回给日客户端,客户端再append到页面上。
客户结果如下:
服务端输出结果:
从上面可以看出,WebSocket在支持它的浏览器上确实提供了一种全双工跨域的通信方案,所以在各以上各种方案中,我们的首选无疑是WebSocket。
上面论述了这么多对于IM应用开发所涉及到的通信方式,在实际开发中,我们通常使用的是一些别人写好的实时通讯的库,比如socket.io、sockjs,他们的原理就是将上面(还有一些其他的如基于Flash的push)的一些技术进行了在客户端和服务端的封装,然后给开发者一个统一调用的接口。这个接口在支持websocket的环境下使用websocket,在不支持它的时候启用上面所讲的一些hack技术。
从实际来讲,单独使用本文上述所讲的任何一种技术(WebSocket除外)达不到我们在文章开头提出的低延时,双全工、跨域的全部要求,只有把他们组合起来才能够很好地工作,所以通常情况下,这些库都是在不同的浏览器上采用各种不同的组合来实现实时通讯的。
下面是sockjs在不同浏览器下面采取的不同组合方式:
从图上可以看出,对于现代浏览器(IE10+,chrome14+,Firefox10+,Safari5+以及Opera12+)都是能够很好的支持WebSocket的,其余低版本浏览器通常使用基于XHR(XDR)的polling(streaming)或者是基于iframe的的polling(streaming),对于IE6\7来讲,它不仅不支持XDR跨域,也不支持XHR跨域,所以只能够采取jsonp-polling的方式。
标签:
原文地址:http://www.cnblogs.com/chunguang/p/5694648.html