Comet:基于 HTTP 长连接的“服务器推”技术解析
一、背景介绍
传统web请求,是显式的向服务器发送http Request,拿到Response后显示在浏览器页面上。这种被动的交互方式不能满足对信息实时性要求高的应用,譬如聊天室、股票交易行情、在线游戏等。Ajax轮询虽然可以解决这个问题,但是会带来增加服务器负担、带宽浪费,并且这种实现方式不够优雅。而Comet技术就是为此而生的。
本文只探讨基于浏览器的web端服务器推技术。服务器推技术在现实应用中有一些解决方案,主要分两类:一类是需要浏览器额外安装插件,基于套接口传送信息或者是基于RMI、CORBA进行远程调用,另一类则无需浏览器安装任何插件,就是本文着重讲的HTTP长连接技术:Coment。Alex Russell(Dojo Toolkit 的项目 Lead)称这种基于 HTTP 长连接、无须在浏览器端安装插件的“服务器推”技术为“Comet”。
写本文的目的:网上的资料比较零碎,博客园的文章基本都是代码+代码,或者直接翻译别人的成果,而对于原理性的东西讲的很少。本文就是给有兴趣或者即将开发服务器推技术的程序员一个技术概览,摸清行内来龙去脉。我比较忌讳对一种新技术,上来就Focus到某个具体的技术上而不能自拔,盲人摸象。我们在定系统框架,敲定技术解决方案之前应该比较各个实现方案技术的优缺点,这样才能做出好的系统来。
二、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