Comet:基于 HTTP 长连接的“服务器推”技术解析
一、背景介绍
传统web请求,是显式的向服务器发送http Request,拿到Response后显示在浏览器页面上。这种被动的交互方式不能满足对信息实时性要求高的应用,譬如聊天室、股票交易行情、在线游戏等。Ajax轮询虽然可以解决这个问题,但是会带来增加服务器负担、带宽浪费,并且这种实现方式不够优雅。而Comet技术就是为此而生的。
本文只探讨基于浏览器的web端服务器推技术。服务器推技术在现实应用中有一些解决方案,主要分两类:一类是需要浏览器额外安装插件,基于套接口传送信息或者是基于RMI、CORBA进行远程调用,另一类则无需浏览器安装任何插件,就是本文着重讲的HTTP长连接技术:Coment。Alex Russell(Dojo Toolkit 的项目 Lead)称这种基于 HTTP 长连接、无须在浏览器端安装插件的“服务器推”技术为“Comet”。
二、Comet实现模型
1.HTTP Long Polling:基于 AJAX 的长轮询方式
众所周知,AJAX可以异步调用XMLHttpRequest对象发送HttpRequest,Javascript响应处理函数根据服务端返回的信息对HTML页面显示进行更新。而使用AJAX实现服务器推与传统AJAX应用的不同之处在于:
a.服务器端会阻塞请求直到有数据返回或者超时;
b.客户端JS响应函数在处理完服务端返回的信息后会再次发出请求,重新建立连接;
如图1所示,因为这种方案基于 AJAX,具有以下一些优点:请求异步发出;无须安装插件;IE、Mozilla FireFox 都支持 AJAX。
图1:基于 AJAX 的长轮询方式
2.HTTP Streaming:基于 Iframe 及 htmlfile 的流方式
通过在 HTML 页面里嵌入一个隐蔵帧,然后将这个隐蔵帧的 SRC 属性设为对一个长连接的请求,服务器端就能源源不断地往客户端输入数据。如图2所示。
图2. 基于流方式的服务器推模型
两种方案对比:
a.在对返回的数据处理方面:方案2(IFrame)返回的是对客户端javascript调用,,如“<script type="text/javascript">js_func(“data from server ”)</script>
”。服务器端将返回的数据作为客户端 JavaScript 函数的参数传递;客户端浏览器的 Javascript 引擎在收到服务器返回的 JavaScript 调用时就会去执行代码。而方案1(AJAX)返回的是数据,通过JS可以很方便的操作DOM。
b.使用IFrame还有一个明显不足:IE、Morzilla Firefox 下端的进度栏都会显示加载没有完成,而且 IE 上方的图标会不停的转动,表示加载正在进行。不过这个问题已经被Google技术人员解决,并应用到Gtalk中,解决方案是:ActiveX 控件解决 iframe 请求长连接时 IE 的加载显示问题,原文地址:“What else is burried down in the depth’s of Google’s amazing JavaScript?”。
三、实际开发时应该注意的问题
对于一个实际的应用而言,系统的稳定性和性能是非常重要的。将 HTTP 长连接用于实际应用,很多细节需要考虑。
这个是由于HTTP 1.1 规范中规定:客户端不应该与服务器端建立超过两个的 HTTP 连接,新的连接会被阻塞。所以,在设计上考虑让多个 frame(AJAX)共用一个长连接。
2.服务器端的性能和可扩展性
一般的WEB服务器是为每个连接创建一个线程,所以在使用Comet时,服务器要维护大量并发,长期存在的长连接。在这种背景下,就需要服务器负载均衡和集群技术,或者在服务端做一些优化。这篇文章:http://rdc.taobao.com/blog/cs/?p=1062的测试表明:200万个Http长连接占用率19G服务器内存,一个大约9kb;所以,你可以算一下你的服务器最多可以维护多少个长连接。
用长连接时,存在一个很常见的场景:客户端网页需要关闭,而服务器端还处在读取数据的堵塞状态,客户端需要及时通知服务器端关闭数据连接。服务器在收到关闭请求后首先要从读取数据的阻塞状态唤醒,然后释放为这个客户端分配的资源,再关闭连接。
所以在设计上,我们需要使客户端的控制请求和数据请求使用不同的 HTTP 连接,才能使控制请求不会被阻塞。
在实现上,如果是基于 iframe 流方式的长连接,客户端页面需要使用两个 iframe,一个是控制帧,用于往服务器端发送控制请求,控制请求能很快收到响应,不会被堵塞;一个是显示帧,用于往服务器端发送长连接请求。如果是基于 AJAX 的长轮询方式,客户端可以异步地发出一个 XMLHttpRequest 请求,通知服务器端关闭数据连接。
客户端不知道何时服务器才有数据传送。服务器端需要确保当客户端不在状态(工作)时,释放为这个客户端分配的资源,防止内存泄漏。因此需要一种机制使双方知道大家都在正常运行。
四、Comet框架介绍
由于Comet早在2006.2月就提出来了,所以并不是什么新鲜玩意,框架还是很多的,譬如:Dojox.cometd, Pushlet,SingalR。
Dojox.cometd:Comet概念的的提出者Alex Russel主导的Dojo项目分支,Dojo 基金会提出了 Bayeux 协议用来标准化 Comet 应用中客户端和服务器端之间的通信。Dojox.cometd 实现了 Bayeux 协议的客户端部分,使用 HTTP 长轮询来作为数据的传输通道。
Pushlet:基于HTTP流,而不是AJAX长轮询。一个开源的 Comet 框架,Pushlet 使用了观察者模型:客户端发送请求,订阅感兴趣的事件;服务器端为每个客户端分配一个会话 ID 作为标记,事件源会把新产生的事件以多播的方式发送到订阅者的事件队列里。貌似只支持Java。
SingalR:基于AJAX长轮询。ASP.NET团队开发的一个开源的.net framework库和Jquery插件。集成的客户端与服务器库,基于浏览器的客户端和基于 ASP.NET 的服务器组件可以借助它来进行双向多步对话。
五、SingalR的MVC4实践
1.安装与运行环境:(Nuget) install-package Microsoft.AspNet.SignalR ,注意:PM>后面不要有空格。
或者到GitHub上下载源码:https://github.com/SignalR/SignalR
这里顺便提一下,SingalR服务端需要支持的OS:
- Windows Server 2012
- Windows Server 2008 r2.
- Windows 8
- Windows 7
- Windows Azure
.net framework4,4.5。WebSocket必须是4.5.
IIS7,7.5 ,integrated Mode,而不是classic Mode, WebSockets必须IIS8。更加详尽的说明,请自行参考:http://www.asp.net/signalr/overview/getting-started/supported-platforms
2.核心实现:
首先,我们需要引用SignalR,用Nuget不需要自己手工添加,应为你的asp.net mvc项目自动添加好一切。
服务端Library:
Microsoft.AspNet.SignalR.Core
Microsoft.AspNet.SignalR.Owin
Microsoft.AspNet.SignalR.SystemWeb
Owin
Newtonsoft.Json
using System; using System.Collections.Generic; using System.Linq; using System.Web; using Microsoft.AspNet.SignalR; namespace DeepleoComet { public class ChatHub : Hub { public void Send( string name, string message) { // Call the addNewMessageToPage method to update clients. Clients.All.addNewMessageToPage(name, message); } } } |
客户端的Jquery插件:
jquery.signalR-1.1.3.min.js.
@{ ViewBag.Title = "Chat"; } <h2>Chat</h2> <div class="container"> <input type="text" id="message" /> <input type="button" id="sendmessage" value="Send" /> <input type="hidden" id="displayname" /> <ul id="discussion"> </ul> </div> @section scripts { <script src="~/Scripts/jquery.signalR-1.1.3.js"></script> <script src="~/signalr/hubs"></script> <script> $(function () { var chat = $.connection.chatHub; chat.client.addNewMessageToPage = function (name, message) { $('#discussion').append('<li><strong>' + htmlEncode(name) + '</strong>: ' + htmlEncode(message) + '</li>'); }; $('#displayname').val(prompt('Enter your name:', '')); $('#message').focus(); $.connection.hub.start().done(function () { $('#sendmessage').click(function () { chat.server.send($('#displayname').val(), $('#message').val()); $('#message').val('').focus(); }); }); }); function htmlEncode(value) { var encodedValue = $('<div />').text(value).html(); return encodedValue; } </script> }
实现效果:
开两个页面:http://localhost:11968/ ,分别键入name,就可以实现一个简单的聊天室功能了。
六、题外话:Comet的替代品:Html5的WebSocket
在浏览器中通过http仅能实现单向的通信,comet可以一定程度上模拟双向通信,但效率较低,并需要服务器有较好的支持; flash中的socket和xmlsocket可以实现真正的双向通信,通过 flex ajax bridge,可以在javascript中使用这两项功能. 可以预见,如果websocket一旦在浏览器中得到实现,将会替代上面两项技术,得到广泛的使用.面对这种状况,HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽并达到实时通讯。
真是相见恨晚啊,才知道还有这么个东西,居然Web端也有了全双工的Socket,貌似有个socket.io还支持IE6,我和我的小伙伴们都惊呆了。
比较好的关于WebSocket文章:http://www.cnblogs.com/wei2yi/archive/2011/03/23/1992830.html
参考资料
1.原理:http://www.ibm.com/developerworks/cn/web/wa-lo-comet/
2.DOJO原理与实践:http://www.ibm.com/developerworks/cn/web/wa-lo-w2fpak-comet/
3.淘宝200W长链接测试与优化:http://rdc.taobao.com/blog/cs/?p=1062
4.长连接百科:http://baike.baidu.com/view/2831907.htm
5.博客园Comet专题:http://kb.cnblogs.com/page/112185/
6. Jetty+Dojo+Tomcat:http://blog.csdn.net/garfier/article/details/5942826
7.SingalR官网:http://signalr.net/
8.Singal辅导:http://www.asp.net/signalr