Asp.net 构建可扩展的的Comet Web 应用(二)
说明
如果你已经阅读了我之前的一篇文章《Asp.net构建可扩展的的Comet Web 应用》。你应该能够理解我将要写的内容。我解释了Comet技术并且解释了怎样用asp.net构建具有可扩展性的应用。然而,我认为之前的的一篇文章写得有点像主线。它展示了足够的技术,但是没有足够包含任何有用的代码。因此,我想我需要写一个API来将之前一篇文章中的功能封装起来。封装为一系列整齐的类,让它们可以被包含到一个通常的web项目中,给你机会去扩展和测试它。
我将不涉及太多关于线程模型的具体细节。因为在之前的一篇文章中涉及了太多关于它的内容。我仅仅讲解涉及到API并且怎样在你的web应用程序中使用它。
我决定写一个轻量级的发送消息的API,它类似于Bayeuxprotocol【Bayeux是在和web服务器间提供低时延、传输异步消息的协议(主要基于HTTP)】交换信息的方式。但是,它没有它没有一个基于改协议的实现当我相信它能够矫枉过正,为了那些被用来需要用这些API工作的需求。并且它也仅仅只是一个草案。
我的原文将我会给出一个小游戏。不巧的是,我觉得用一个简单的聊天程序来说明它可能更加容易。这个程序使用一个Comet通道来接受信息,用一个WCF服务来发送信息。
基本的聊天程序:
术语表
下面是我这篇文章中使用的列表,以及他们的描述。
通道:这是一个Comet客户端能够连接到的结束点。任何发送到客户端的信息必须被转交给通道传输。
超时:当一个客户端已经连接到一个通道,并且过了预定义的一段时间内,还没有收到任何的消息。当超时的时候客户端可以重新建立连接。
闲置客户端:这是一个客户端没有连接到服务器的时限。一个闲置的客户端在一个预设的时间后将被断开。
消息:通过一个通道发送给一个客户端的JSON格式的消息。
订阅:一个客户端被订阅到一个通道。他们被连接并且准备接受消息。
项目的代码
项目的代码包含所有的能够在你的asp.net项目中使用Comet类。代码和原文中设计的代码非常接近。但我扩展了功能,使得能够在客户端和服务端传输通常的消息。
控制Comet主要的类是CometStateManager。这个类管理单个的通道。这个类使用ICometStateProvider接口的一个实例以一种特别的方式来为你的应用程序管理状态。在API中,有一个内建InProcCometStateProvider的实现,用来在服务器的内存中存储状态。很显然,这不是实现负载均衡环境的一个好做法。但你可以实现一个自定义的提供程序(provider),使用数据库或者一个自定义的状态服务器。
为了向外部暴露你的通道,需要用一个IHttpAsyncHandler的实现来包装它。我最终试图使用一个WCF的异步模型。但发现它不会释放asp.net工作线程,如同使用异步handler一样。这有点可惜,这并不是期望的。
下面的代码展示了你应该怎样去建立一个IHttpAsyncHandler来为你的Comet通道提供一个结束点。
上面的代码足够简单。我们有一个CometStateManager类的静态实例。它被用来构建ICometStateProvider的实例。在这个例子中,我们使用一个内置的InProcCometStateProvider的实现。
这个类其余的实现是简单得将BeginProcessRequest和EndProcessRequest方法映射到CometStateManager 类实例的BeginSubscribe和EndSubscribe方法。我们也需要在web.config中配置handler才能使用它。
这个通道现在已经可以被客户端订阅了
CometClient类
通道需要与客户端保持连接。每一个客户端都代表一些被CometClient的实例排序的缓存。我们不希望任何旧的客户端连接着服务器或者任何没有被认证的客户端注册通道。所以我们希望实现一种授权和认证机制。也许是asp.net标准的 Form认证,或者也许是一个WCF调用一个服务来验证凭据,并且在我们的通道中实例化一个客户端。
下面的代码展示了default.aspx页面的登录操作:
我们没有验证密码或任何其他的东西,我们只是在页面上直接输入用户名,并且用它来区分不同的客户端。一个Comet客户端有两个token供API使用:
PrivateToken这个token是客户端私有的,被用来注册消息到客户端。
PublicToken这个token被用来区分是哪一个客户端。它通常在发送消息到一个特殊的客户端时被使用。
我们使用一个公共令牌和一个私有令牌的原因是,私有令牌能够被用来注册一个通道以及从别的用户那里接受消息。我们不想任何其他的客户端能够隔离原本的客户端(例如我们不希望消息欺骗)。出于这个原因,如果我们想在两个客户端之间发送消息我们使用公共令牌。
为了简单起见,我已经在客户端包含了一个DisplayName的属性来存储用户名。
为了在客户端建立一个通道,你需要调用InitializeClient。在上面有显示。这个方法携带了下面的一些参数:
publicToken – 客户端公共令牌
privateToken – 客户端私有令牌
displayName – 客户端显示名称
connectionTimeoutSeconds – 连接超时时间
connectionIdleSeconds- 在服务器杀掉一些闲置客户端从而等待一个客户端重新连接的秒数。
上面的例子中,InitializeClient
会调用。从表单中指定用户名作为公共令牌,私有令牌以及显示名称。这不是非常的安全,但对于一个Demo来说已经足够了。为了使它更加安全,我本可以产生一个GUID来作为私有令牌。并且使用公共令牌作为用户名。
InitializeClient
将通过ICometStateProvider被调用。一个新的InitializeClient实例化了CometClient类。并且期望它被存储在缓存中。
随着CometClient
的可访问,客户端可以使用它们自己的私有令牌来订阅通道。
客户端 JavaScript
为了实现客户端的功能,有一个文件在项目中,Scripts/AspNetComet.js包含了所有的需要订阅通道的js(以及公有的JSON转换器)。为了使一切变得简单一点,我在CometStateManager中包含了一个静态方法来调用RegisterAspNetCometScripts
。它接受一个带有参数的页面并且在页面上注册了脚本。
随着它被调用,我们就能够很自由得使用我们能够使用的客户端API。下面的例子摘自项目中的chat.aspx。并且展示了一旦一个客户端被实例化,你应该怎样订阅一个通常的通道。
客户端API所有的功能都被包裹到一个被叫做AspNetComet的JavaScript类中。这个类的一个实例被用来跟踪一个已连接的客户端的状态。被要求订阅的是Comet结束端的handler的URL。CometClient的私有令牌,以及一个别名被用来区别客户端通道。一旦我们构建一个AspNetComet的实例,我们可以在Comet的生命周期内定义一系列的handler以供在特殊时刻调用。
addTimeoutHandler–
当一个客户端等待过了一个预定的事件并且没有接收到消息调用该handler。
addFailureHandler–
当一个Comet调用失败,其中一个失败的例子就是Comet客户端没有被连接,调用该handler。
addSuccessHandler–
每一个消息被发送到客户端的时候handler被调用。
接下来的代码展示了每一个handler方法的签名:
SuccessHandler
的参数是CometMessage类的一个实例。下面的代码显示了类和它的JSON格式:
发送一条消息
在这个聊天的应用程序中,我已经包含了一个能使用AJAX的WCF web 服务来扮演发送消息功能的结束点。下面的代码显示了点击发送消息按钮的客户端事件的处理器:
这段代码构建了一个由asp.net Web Service framework创建的 ChatService
客户端对象的实例。然后仅仅调用了SendMessage方法,通过传递客户端的私有令牌和消息。
SendMessage
代码携带参数以及写了一条消息给所有的客户端。下面的代码展示了功能:
这个方法从私有令牌中查找CometClient,并且创建了一个被用来作为消息内容的ChatMessage的对象。这里的消息内容通过CometStateMessager的实例的SendMessage方法被发送到每一个连接着的客户端。它将处罚任何连接着的客户端来回调包含在chat.aspx页面里的SuccessHandler方法。它将信息写到页面的聊天区域。
使用这段代码
解决方案中的网站执行的时候不需要改变任何的配置,仅仅连接一些客户端到应用程序中。并且聊天信息应该被实时发送。
使用这个API将使你能在你的AJAX程序中使用一个Comet风格的方案。使用WCF能使你发送消息到服务器,这些都已经为你自动包装了。然后仅仅只是在一个Comet通道中回调来连接到客户端。