Asp.net 构建可扩展的的Comet Web 应用(一)

说明

这篇文章用来提供在asp.net中使用comet的一种理论上的解决方案。它包含了Comet技术在服务端的实现以及怎样去解决可扩展的问题。我将在不久以后发表一般文章,使用我接下来要讲到的Comet
线程池技术演示一个小游戏,来提供客户端的代码。它可能会给你在真实的环境下解决问题带来一些思路。

简介

在过去的六个月里,我一直都在投入精力开发一个在线的象棋应用程序。它能够让玩家注册、登陆,并且像在真实世界中对弈一样。其中,我不得不克服的一个障碍就是,怎样在服务端和客户端实现一个类似在真实世界中的通信。要克服这个障碍,以下一系列的因素需要考虑:

(1) 可扩展性 – 我想让它在一个负载均衡的环境中工作,并且不需要占用巨大的服务器资源。

(2) 兼容性 – 我希望它能够在许多不同特性的浏览器中工作,希望不需要任何的浏览器插件。

(3) 性能 – 我希望在一位玩家到任何可通信的对手之间提供尽可能快的响应。这可以让我更有效得控制时间和提供一个更友好的用户体验。

(4) 简单 – 我想实现通信层儿不想涉及第三方服务应用程序。总得来说,它应该仅仅工作在宿主环境中,例如www.discountasp.net

我评估了以上所有我列出的选项。我构建的第一个原型是使用标准的ajax作为解决方案。它从服务端“拉取”数据。这造成了太长得延时并且太多次数的通信。因此,我很快地从可行性方案中把它移除掉了。我又调研了其他的通信方案。例如使用一个隐藏的Flash小程序进行socket通信。这需要一个浏览器插件,所以也不是我想要的答案。然后我发现了Comet,并且觉得这就是我想要的方案。于是,我做了一些调研,并且构建了一个原型。

Comet的技术原理

Comet在客户端(web浏览器,使用XmlHttpRequest)和服务端使用一个持续的连接。这个持续连接在一段预定义的时间内(例如5秒钟)对服务端保持着打开状态,并且将会客户端两种响应:要么超时信息,要么是服务端应用程序的某些部分的逻辑想要发送的信息。一旦客户端接收到信息,它将可以被实现在客户端的任何应用程序的逻辑处理。这个持续的连接然后会被重新打开,并且重复以上过程。

这种方案解决了性能的问题。它意味着无论什么时候一条消息只要需要被发送,它就可以被发送会客户端。并且如果这个持续的连接被打开着,客户端接收它只需要很短的延时,几乎是瞬间的。

另一个连接被用来发送信息给服务器。这个连接并不是“持续”的。并且通常情况下处理完之后立即返回。从这个象棋游戏的一端看来,这个持续的连接一直都在等待对手棋子的移动。而那个非持续的连接则只是发送我的移动。

一个 Comet请求返回超时的顺序图

一个Comet返回消息的顺序图

真实环境中使用Comet

到现在为止,一切从纸面上看起来都很美好。我们有一种方案能够提供发送信息给一个浏览器的功能,在真实的环境并且不需要插件。但是在实践的过程当中,会遇到更多的麻烦。许多文章都在描述这样一个事实——采用一个持续的连接多么得”Hack”。我不太倾向于同意这样的观点。

Comet在某些浏览器中运行,确实会遇到一些问题(主要是因为HTTP协议规定,每个浏览器只同时支持两个连接,并且限制在同一主机上)。Http协议的这个限制被用来为通常的在低带宽连接下的浏览提供更好地性能,这在运行Comet程序的时候导致了性能问题(有一些解决方法)。这个问题,仅仅只需要被IE关注(我猜想IE直到8.0的版本,才严格执行了标准)。FireFox
2运行更多的连接,并且将他们管理得更好。另外,FireFox 3甚至允许更多的连接,这意味着Comet风格的应用程序的前景是光明的。

第二个问题来着这种技术的可扩展性。这也是这篇文章尝试补救的问题。这个问题源于现阶段各个平台缺乏对Comet这种风格协议的很好的支持,这导致了使用了持久连接的应用程序在未来可能不会具有很好的扩展性。我想说,这不是Comet技术思想本身的失败,而是对Comet服务器特殊实现的失败。

很多其他的开发者已经将那些坐在我们平台之前的服务器放到了一起。这就允许我们将Comet的请求方式从web
服务器中分离开来。并且通过管理他们自己的持续连接可以实现扩展。我在这篇文章中阐明的就是你在哪些情况下不应该在asp.net中使用Comet,并且给出了可能的解决方案。

开始测试Comet

在asp.net中使用持续连接最主要的缺陷是,在asp.net中每一个连接都占用asp.net一个工作线程(那些连接可能持续着五秒,并且一直打开着)。因此,每一个客户端连接都将在asp.net线程池中占用一个线程。最终,无法卸载,服务器将停止响应。

为了展示这种情况,我举一个非常简单的例子。它使用很简单的持续连接向asp.net发起请求。用一个handler占用请求,并在返回客户端之前使其保持打开状态5秒钟。

这个handler非常的简单,它持有请求的执行5秒钟。然后返回。这个简单的Comet请求将最终因为请求超时返回到客户端。

我还写了一个控制台程序。使用WebRequest来调用CometSyncHandler。结果和预期的一样,每一个客户端占用了一个asp.net工作线程,最终在40或者更多的连接下,网站开始招架不住,页面渐渐响应非常缓慢。

下面的截屏可以看到发生了什么:

可以清楚地看到,它不适合任何真实的应用程序。所以我做了一些“挖掘”,并且设计了一个解决方案。

IhttpAsyncHandler

这是方案的第一部分,这个小“魔法”在当我们向一个handler发出一个请求时。

,允许我们在服务器上异步地运行代码。如果你对IasyncHttpHandler不是很熟悉,那就阅读我下面的解释,来了解它如何工作:

IhttpAsyncHandler
开放两个主要的需要被实现的方法。它们是:BeginProcessRequest和EndProcessRequest。通常的做法是:我们在请求开始的时候的处理逻辑放在BeginProcessRequest中,然后我们执行一系列的异步方法,例如数据库查询或者.net的异步方法。当这些异步方法执行完成之后,然后响应客户端的处理放在EndProcessRequest。

下面的顺序图,展示了它如何工作:

CometThreadPool

上面的顺序图介绍了一个自定义的线程池用来处理Comet请求。需要它的原因是因为我们不想asp.net为每一个这样的请求开启一个它自己的线程,知道它需要等待一个Comet请求的超时。

这段线程池技术的实现技术位于网站的CometAsync文件夹中。它包含了如下的文件:

CometAsyncHandler – 这是一个IhttpAsyncHandler接口的实现。

CometAsyncResult – 这是IasyncResult接口的自定义实现。它包含了一个Comet异步操作的状态。

CometThreadPool – 这是一个静态类用来管理Comet线程池。

CometWaitRequest – 这是一个代表从客户端请求的对象。它们被排列自定义线程池中等待被处理。

CometWaitThread –这是一个线程用来处理来自队列中的CometWaitRequest对象。

这个实现在第一次创建一系列的后台CometWaitThread对象。每一个这些对象都包含一个单独的线程,用来处理CometWaitRequest。在我们的web应用程序中,我们将在Application_Start实例化一个线程池。

这个被创建的五个线程一直被闲置在后台,直到等待CometWaitRequest实例的到来,以为这些实例提供服务。

然后CometAsyncHandler等待来自客户端的请求。它的职责是将这些请求排列在线程池中。

为了我们能完全的跟踪哪些线程是存在的,BeginProcessRequest输出了一些debug信息。然后创建了CometAsyncResult类的一个实例来跟踪HttpContext,并且向asp.net返回并说明它已经开始了一个异步处理。在返回之前,它调用了BeginWaitRequest,用来把请求加入线程池。

这段代码创建了一个CometWaitRequest类的新的实例并且把它排列到线程池中。

这段代码逻辑中,挑选了一个CometWaitThread并基于循环方法来分配CometWaitRequest(例如,如果线程1接收之前的一个请求,线程2将接收第二个)。

CometWaitThread类

请求被加入到一个被选中的线程的用来存放CometWaitRequest对象的列表中。

这一时刻,CometAsyncHandler已经将asp,net线程返回到线程池中。并且正在等待CometWaitThread完成异步处理,然后它就可以完成客户端的请求。CometWaitThread的代码看起来像下面这样:

QueueCometWaitRequest_WaitCallback是这个线程的入口点。它在Application_Start方法返回的时候开始执行。它执行了一个循环,并等待一个CometWaitRequest对象加入到它的队列中。一旦一个客户端请求CometAsyncHandlerhandler,它就会出现在队列中。

它顺序处理队列中的每一个对象,在每一次循环中。例如,如果这里有三个请求。它将检查请求1,2,3 然后继续循环并且处理继续处理请求1,2,3
。这能够确保每一个请求都被尽可能快地处理而不是等到它的5秒超时完成。

循环检查是否CometWaitRequest已经是否排列在队列中的时间已经超过了它预定的5秒超时时间。否则,它会检查是否有一个事件正在等待被返回给客户端。如果,这两种情况都不是,它就完成了CometWaitRequest的处理,返回所需的响应对象。然后从队列中将其移除。

QueueCometWaitRequest_Finished方法完成异步操作,通过调用CometAsyncResult
对象的SetCompleted方法。然后调用CometAsyncResult上的回调方法。它指向CometAsyncHandler对象的EndProcessRequest方法。接下来的代码会被执行。

该方法以序列化任意的我们设置到request's HttpContext输出流的对象来响应客户端。

有一个需要被提及的事情是,无论那些线程最终对请求做什么处理,当它到达BeginProcessRequest方法时,它是一个正在被执行的asp.net工作线程。并且当CometWaitThread完成时,要么是一个超时信息要么是一个返回信息,EndProcessRequest方法是被CometThreadPool线程池中的一个线程执行的。这意味着,asp.net只是使用了它线程池中的一个线程来初始化Comet请求。其余的5秒不是被asp.net线程处理。

我们在执行时,屏幕的截图中可以看到:

从这点上来看,值得一提的是来自网站的输出是非常棒的。考虑到这里有200个持续连接,可以看到任务管理器重的CPU/内存数据也是非常正常(并且还同时在这台机子上运行着客户端)。

为了检查一切都工作地非常正常,我为每一个请求/响应对做了一个计数器,来确保每一个请求都有一个响应。下面的截图显示了运行着200个客户端五分钟的测试输出。

它显示了所有的请求都完成地非常得成功

结论

通过实现一个客户端的线程池,我们能构建一个Comet的解决方法在我们的asp.net服务端代码而不是实现一个自定义的服务器,或者甚至实现任何复杂的消息程序。只是一个简单的线程池来管理对个请求。例如我们用五个线程来管理了所有的200个Comet请求。

原文链接:http://www.codeproject.com/KB/aspnet/CometAsync.aspx

原文发布时间为:2011-08-31

时间: 2024-10-02 00:05:43

Asp.net 构建可扩展的的Comet Web 应用(一)的相关文章

Asp.net 构建可扩展的的Comet Web 应用(二)

说明 如果你已经阅读了我之前的一篇文章<Asp.net构建可扩展的的Comet Web 应用>.你应该能够理解我将要写的内容.我解释了Comet技术并且解释了怎样用asp.net构建具有可扩展性的应用.然而,我认为之前的的一篇文章写得有点像主线.它展示了足够的技术,但是没有足够包含任何有用的代码.因此,我想我需要写一个API来将之前一篇文章中的功能封装起来.封装为一系列整齐的类,让它们可以被包含到一个通常的web项目中,给你机会去扩展和测试它. 我将不涉及太多关于线程模型的具体细节.因为在之前

ASP的几大可扩展组件(一)

    ASP的几大可扩展组件(一)     对于ASP的扩展组件,有些大家已耳熟能详,有些则还陌生的很,写这篇文章的的意图无非想抛砖引玉,在各位对ASP几大对象已烂熟于心时,不妨玩玩一些不常用的可扩展组件,相信会对各位的ASP编程有很大的启发.(这篇文章的内容来自MSDN,对其了若指掌的朋友自不必看.还有,小妹的耐 心不好,如果写完了这篇没有长性就此罢笔,望各位在大骂的时候留点口德,多谢多谢.):b     Browser Capabilities Component--浏览器性能组件    

ASP的几大可扩展组件(二)

ASP的几大可扩展组件(二)     上回说到浏览器性能组件(Browser Capabilities Component)的使用方法,本回接着前文,介绍如何编写或升级Browsercap.ini文件. Browscap.ini文件     你可以在Browscap.ini文件中描述任意多个浏览器的属性.你也可以设置一套默认属性,当浏览器发送的报头无法和Browscap.ini中的任何条目匹配时,浏览器类型(BrowerType)对象会假设该浏览器具备默认的属性.     每个浏览器定义由一个H

初步了解ASP.NET的AJAX扩展

下载了一个 ASP.NET AJAX Beta 版,卸载原来的 Atlas,执行安装过程安装到\Program Files\Microsoft ASP.NET目录下.区别是 Microsoft.Web.Extensions.dll 被安装到 GAC 中,并且在安装指引中有这样一段话:"The installation package installs the assembly (Microsoft.Web.Extensions.dll) in the Global Assembly Cache

构建可扩展的Java EE应用(一)

对于一个具备使用价值的应用而言,其使用者有可能会在一段时间内疯狂的增 长.随着越来越多的关键性质的应用在Java EE上运行,很多的Java开发者也开始 关注可扩展性的问题了.但目前来说,大部分的web 2.0站点是基于script语言编 写的,对于Java应用可扩展能力,很多人都抱着质疑的态度.在这篇文章中, Wang Yu基于他本身在实验室项目的经验来展示如何构建可扩展的java应用,同时 ,基于一些在可扩展性上做的比较失败的项目给读者带来构建可扩展java应用的 实践.理论.算法.框架和经

使用React + Redux + React-router构建可扩展的前端应用

现在是前端开发最好的时代,有太多很好的框架和工具帮你更好的实现复杂需求;同时又是最困难的时代,因为需要掌握太多的框架和工具.如何利用好各种框架来提高前端开发质量是大家都在探索的问题.本文就将介绍如何使用 React 及其相关技术,来进行实际前端项目的开发.因为主要介绍如何将技术用于实践,所以希望读者已经对相关概念已经有一定的了解. 本文最初来源于笔者在 StuQ 的一次同名课程直播,现在加以整理成文,希望能对更多的人有所启发.为了固化这种实践方式,当时还开发了一个名为 Rekit 的工具,用于确

asp.net学习之扩展GridView

原文:asp.net学习之扩展GridView    本节讨论如何从现有的控件,进而扩展成强大的,更定制的GridView控件 1.扩展BoundField        默认的BoundField不能显示多文本,文字一多,就会扩大整个Table的Height值,解决这个问题的方法可以通过TemplateField加入Div控件来解决,但是,也可以从BoundField类上进行扩展,加入一点特有的功能,让他能够显示多文本 例1: 创建长文本字段 ===App_code\myControls.cs

面向Java开发人员的Ajax: 使用Jetty和DWR实现Comet Web应用程序

作为一种广泛使用的 Web 应用程序开发技术,Ajax 牢固确立了自己的地位,随之而来的是一些通用 Ajax 使用模式.例如,Ajax 经常用于对用户输入作出响应,然后使用从服务器获得的新数据修改页面的部分内容.但是,有时 Web 应用程序的用户界面需要进行更新以响应服务器端发生的异步事件,而不需要用户操作 -- 例如,显示到达 Ajax 聊天应用程序的新消息,或者在文本编辑器中显示来自另一个用户的改变.由于只能由浏览器建立 Web 浏览器和服务器之间的 HTTP 连接,服务器无法在改动发生时将

返璞归真 asp.net mvc (10) - asp.net mvc 4.0 新特性之 Web API

原文:返璞归真 asp.net mvc (10) - asp.net mvc 4.0 新特性之 Web API [索引页][源码下载] 返璞归真 asp.net mvc (10) - asp.net mvc 4.0 新特性之 Web API 作者:webabcd 介绍asp.net mvc 之 asp.net mvc 4.0 新特性之 Web API 开发一个 CRUD 的 Demo,服务端用 Web API,并使其支持 jsonp 协议,客户端用 jQuery 示例1.自定义一个 JsonMe