`
leonardleonard
  • 浏览: 801357 次
社区版块
存档分类
最新评论

使用 .NET 实现 Ajax 长连接

阅读更多

作者:http://www.cnblogs.com/cathsfz/

 

Ajax的长连接,或者有些人所说的Comet,就是指以XMLHttpRequest的方式连接服务器,连接后服务器并非即时写入相应并返回。服务器会保持连接并等待一个需要通知客户端的事件,该事件发生后马上将数据写入响应,这时候客户端就以相当“实时”的方式接收到事件通知。具体的通信模型,请参考这篇文章:《Comet:基于 HTTP 长连接的“服务器推”技术》,里面已经说得非常详细了,我就不再复述了。

我们接着开始讨论如何使用.NET实现这个模型。首先我们能想到的是,我们需要一个Web Service,可以是ASP.NET Web Service,也可以是WCF Web Service,ASP.NET AJAX Library两者都支持。在这里,为了简单起见,就选择大家更熟悉的ASP.NET Web Service举例。然后,我们写下以下两个函数签名:

public void Send(Message message);
public Message Wait();

其中,Send函数用来发送一个Message对象,而Wait函数用来等待一个Message对象。然后,让我们来讨论一些细节问题。

无事件导致超时

首先,长期保持连接时不行的。对于服务器和客户端来说,这不是个问题,但我们永远都要记住中间可能存在各式各样配置怪异的网关和代理,它们上面可能有各式各样的超时规则,因此Comet最好设计为定期重连。一般情况下,如果30秒没有任何事件发生,服务器端就应该通知客户端确实没有事件发生,结束掉本次请求,然后重新开始一次新的请求以便继续等待。

那么上述函数签名可否用来返回一个无事件的消息呢?这是显然可以的,我们可以选择返回null表示无事件,或者返回一个EmptyMessage常量,这视乎我们使用class还是struct来定义Message。(甚至,我们还可以做一个名为NoMessageMessage的Message派生类来做这个事情。)

定义发送目标

上述函数签名确实能用来收发消息,但是没指名发给谁。可能有人会说,发送给谁可以在Message类里面通过一个属性来定义啊。但是Wait()方法没有说明接受方是谁,服务器端依然不知道哪些消息应该让你接收。

因此,我们引入Channel的概念,Channel使用其名称来标识,相同名称的就必然是同一个Channel。在发送与接受时,通过名称指定要发送到哪个Channel,这样问题就解决了。此时,函数签名修改如下:

public void Send(string channelName, Message message);
public Message Wait(string channelName);

可靠的消息队列

想象一个可能发生的情况,服务器端向你发送一个消息,你没有成功接收,但是服务器端认为发送了就成功了,消息从队列删除了,然后这个消息就永久丢失掉了。可能有人会强调TCP多么可靠,服务器端发送的消息如果在TCP的层面发生问题了,肯定会引发Socket级别的Exception,这个Exception冒泡上来,服务器端就能截获,从而得知发送失败,然后先不删除队首消息。可是别忘了,中间是可能存在代理的,如果代理成功把消息收回去了,可是代理发送到客户端这一步失败了,服务器端就不一定会发生异常了。

因此,我们需要制定一种策略,来确保下行消息总能发送到客户端。在这里,我们选择了引入逐个ACK的机制,来确认消息的接收。也就是说,服务器端发送给客户端的消息带有一个序号,在客户端收到消息后就将该序号发回给服务器端,已确认它受到了该消息。这时候,函数签名更改如下:

public int Send(string channelName, Message message);
public Message Wait(string channelName, int sequence);

我们使用Wait()接收到的Message中,应该有一个Sequence的属性,标记它的序号。然后,再我们执行下一次Wait()时就将该序号加1的值通过sequence参数传递回去,让服务器知道我们期望下一条消息的编号是这个。例如我们收到Message,其Sequence属性为836,那么下一次调用Wait()的时候就传给服务器837。服务器端此时应该保留了编号为836的Message在对首,如果客户端继续请求836号消息,证明它上次没收到,这次仍然发送836号消息给它;如果客户端请求837号消息,证明它成功收到836号消息的,这次就发送837号消息给它。

如果都不是,那该怎么办?那意味着,这是一个错误的请求,甚至可能是攻击请求,因为正常情况下不应该出现这样的请求的,服务器端可以考虑抛个无关紧要的Exception(不要告诉攻击者你知道他在攻击了),甚至直接给个400 (bad request)的响应代号。

与Wait()类似的,Send()也可以加入ACK机制,只需要将返回类型从void改为int就可以了,这个值就专门用于传递消息编号,实现方式和Wait()是一样的,不过Send()是由客户端保存待发送消息的队列。

小结

到此为止。我们的Web Service就写好了。这就写好了?只有签名没有函数体?是的,复杂的工作留给model去做,Web Service在这里只是相当于一个view,用于将model的接口暴露出来。

在下一次的文章中,我们将开始讨论如何实现服务器端的消息传递机制。

 

在上一次的文章中,我们说到了如何设计一个ASP.NET Web Service来处理长连接请求。很多人对此就提出了问题,如何hold住请求让它30秒不断开了?这其实很简单,只需要Sleep()一下就可以了:

Thread.Sleep(30 * 1000);

然而问题是,我们不是要等30秒然后看看是否有事件需要返回,而是在这30秒内随时有事件随时返回。因此,我们需要一套机制来在等待的过程中检查是否有事件发生了。

Monitor模型

在.NET里面,大家最熟悉的线程同步模型应该就是Monitor模型了。没听说过?就是C#的那个lock关键字,实际上它编译出来就是一对Monitor.Enter()和Monitor.Exit()。

通过lock命令,我们可以针对一个对象创建一个临界区,代码执行到临界区入口时必须获取到该对象的锁才能执行下去,并且在临界区的出口释放该锁。然而这种模型不太适用于解决我们的问题,因为我们需要等待一个事件,如果使用lock来等待的话,那就是说要先在Web Service外部把对象锁上,然后等事件触发了就解锁,这时候Web Service才顺利进入临界区域。

事实上,要进行这类型的阻塞,还有一个更好的选择,那就是Mutex。

Mutex模型

Mutex,也就是mutual exclusive的缩写,“互斥”的意思。Mutex是如何运作的?这有点像是银行的排队叫号系统,所有等待服务的人都坐在大厅里等候(wait)被叫,当一个服务窗口空闲时它就会发出一个信号(signal)来通知下一位等候服务的人。总之,所有执行wait指令的线程都在等候,而每一个signal能够让一个线程结束等候继续执行。

在.NET里面,wait和signal这两个操作分别对应Mutex.WaitOne()和Mutex.ReleaseMutex()这两个方法。我们可以让Web Service的线程使用Mutex.WaitOne()进入等候状态,而在事件发生时使用Mutex.ReleaseMutex()来通知Web Service线程。因为必须在Mutex.ReleaseMutex()发生后Mutex.WaitOne()才可能继续执行下去,因此能够执行下去就证明必然有事件发生了并且调用了Mutex.ReleaseMutext(),这时候就可以放心地去读取事件消息了。

简单示例

在选定使用Mutex模型后,我们来编写一个简单的示例。首先,我们要在WebService派生类内定义一个Mutex,还有一个代表消息的字符串。

Mutex mutex = new Mutex();
string message;

然后,我们定义两个WebMethod。为了把问题简单化,我们选用上一篇文章中开头所说的两个函数签名,也就说只能在一个Web Service内自己发自己收,没有发送目标的概念,也没有超时的概念,还没有可靠性设计。同时,我们将Message类型替换为普通字符串,以便于我们测试。

我们先编写发送消息的函数:

public void Send(string message) {
  this.message = message;
  this.mutex.ReleaseMutex();
}

在这个发送函数里,首先我们把消息放进了类内全局的变量中,然后让全局的Mutex类释放一个signal。这时候,如果有线程在等待,它可以马上执行下去。如果此时没有线程在等待,那么下一个wait的线程执行到该阻塞的地方就能够不受阻塞继续执行下去。

现在我们来编写接收消息的函数:

public string Wait() {
  this.mutex.WaitOne();
  return this.message;
}

接收函数一开始就进入wait状态。在得到signal后,需要做的事情就是把全局的消息返回给客户端。

亲身体验

最后,我们可以通过ASP.NET Web Service本身支持的Web测试界面来测试一下我们的代码。我们开两个浏览器窗口,一个进入Send()调用,一个进入Wait()调用。然后我们按照如下方法来测试:

  1. 首先执行Send("Hello"),然后执行Wait()。这时候你可以马上看到"Hello"。
  2. 首先执行Wait(),让它等待返回,这时候执行Send("Hello")。随后你可以看到Wait()那段返回"Hello"了。
  3. 按如下顺序执行:Send("Hello");Wait();Send("World");Wait();
  4. 按如下顺序执行:Send("Hello");Send("World");Wait();Wait();
  5. 按如下顺序执行:Wait();Wait();Send("Hello");Send("World");
  6. 按如下顺序执行:Wait();Send("Hello");Wait();Send("World");

你会发现这样一些奇怪的结果:第3个测试返回的是"World"和"World"。第5个测试先返回"Hello"的并不一定是先执行的那个Wait()线程。后者在某些情况下不是什么问题,特别是长连接中一般之后一个Wait()线程在等待中,所以我们可以不管。而前者,则是因为没有消息队列所造成的,我们只有长度为1的消息窗口,所以只能缓存最后一个消息。这个问题我们将在下一篇文章中解决。

小结

在本文中,我们看到了不同的线程同步模型的差异。Monitor模型的lock本质上是一个Semaphore,也就是一个不能连续signal的Mutex,一个signal发出去后必须被一个wait接收了才能进行下一次的signal。同时,Semaphore也限制了signal和wait必须在同一个线程内成对执行,而Mutex则没有此限制。虽然.NET是针对Monitor模型优化的,但在我们的需求当中,只能通过Mutex模型来解决。

接着,我们便写了一个小小的消协发送与接收函数,实现了我们想要的阻塞式Web Service。同时我们也看到了没有消息队列造成的问题,因此确定接下来我们要做一个消息队列。

分享到:
评论

相关推荐

    AJAX+asp.net 实现的在线聊天

    总结来说,AJAX+ASP.NET实现的在线聊天室利用了AJAX的异步特性,提高了用户体验,而ASP.NET提供了强大的服务器端框架支持。结合SignalR等库,我们可以创建出实时、高效的聊天应用。开发过程中要注意用户体验、安全性...

    一个优秀的基于ASP.NET+ajax实现的无刷新版本聊天室系统源码

    `Web.config`是ASP.NET应用的配置文件,它包含了应用程序的配置信息,如数据库连接字符串、安全性设置、错误处理策略等。开发者可以根据需求调整这些设置来定制应用的行为。 综上所述,这个聊天室系统充分利用了ASP...

    ASP.NET3.5 AJAX客户端编程精选166例(使用C#)

    ### ASP.NET 3.5 AJAX 客户端编程精选166例(使用C#) #### 一、ASP.NET 3.5与AJAX简介 ASP.NET 3.5是微软发布的一个重要的Web开发框架,它基于.NET Framework 3.5,提供了丰富的工具和API来帮助开发者构建动态的...

    asp.net+ajax实现无刷新聊天室

    3. **jQuery和AJAX**:虽然可以纯JavaScript实现AJAX,但通常我们会使用jQuery库,因为它简化了AJAX调用。例如,使用`$.ajax()`或`$.get()`、`$.post()`方法可以更方便地发起请求,并处理响应。 4. **Web服务或API*...

    Asp.Net基于Ajax的无刷新聊天室

    Asp.Net基于Ajax的无刷新聊天室,可以带来以下两点好处: ·页面实时更新,无需完全刷新页面; ·聊天内容更新时,只需要读取最新的聊天信息,做到“按需取数据”。 本项目将会实现以个基于Ajax的无刷新聊天室,其...

    Asp.net+Ajax做的无刷新聊天室

    本项目“Asp.net+Ajax做的无刷新聊天室”充分利用了两者的优势,实现了用户实时聊天和在线用户列表展示的功能,无需页面刷新。 1. **Ajax的核心原理** Ajax的核心在于创建XMLHttpRequest对象,通过这个对象与...

    基于.Net和AJAX技术的留言本

    在.NET平台上,开发者通常使用C#或VB.NET作为编程语言,ASP.NET作为Web应用开发框架,而AJAX则通过JavaScript库(如jQuery)在客户端实现页面局部更新。 首先,.NET框架提供了全面的开发工具和类库,用于创建Web...

    JQ asp.net ajax 用户注册

    本项目“JQ asp.net ajax 用户注册”旨在实现一个简单的用户注册流程,利用jQuery(简称JQ)与ASP.NET AJAX技术,提供前端交互体验和后端数据处理。以下将详细介绍该系统的核心知识点: 1. **jQuery (JQ) 介绍**: ...

    ASP.NET 长连接做得在线聊天例子

    在这个"ASP.NET 长连接做得在线聊天例子"中,我们将深入探讨如何利用ASP.NET技术实现一个实时的在线聊天系统,主要关注长连接的概念及其在聊天应用中的应用。 一、ASP.NET长连接 传统的HTTP协议是基于短连接的,即...

    Ajax 操作实例 ASP.NET实例

    在代码-behind文件中,我们可以使用SqlDataSource或ObjectDataSource来连接数据库并填充GridView。确保在GridView中设置适当的事件处理程序,如RowUpdating和RowDeleting,以便在用户操作时触发Ajax请求。 接下来,...

    asp.net下简单使用ajax

    在纯 JavaScript 中,我们可以使用 `XMLHttpRequest` 对象来实现 AJAX 请求。首先创建一个 XMLHttpRequest 实例,然后设置请求的类型(GET 或 POST)、URL 和是否异步执行。之后打开连接并发送请求。当服务器响应时...

    Ajax全新教程 无刷新页面 无刷新数据更新 无刷新绑定 asp.net全新Ajax教程

    在ASP.NET中,可以使用Ajax来实现视图(View)与数据源(DataSource)之间的动态绑定,即使在用户交互时,也能实时反映出数据的变化。这在诸如表格、列表等需要实时更新的数据展示组件中非常实用。 **ASP.NET全新...

    征服ASP.NET 2.0 Ajax之CascadingDropDown关联菜单的使用实例

    在ASP.NET中,我们可以使用`SqlDataSource`控件连接到SQL Server数据库,通过执行SQL查询或存储过程获取数据,再将其提供给CascadingDropDown控件。这种方式更加灵活,适用于处理大量动态数据。 4. **Ajax回调**:...

    Asp.Net Ajax GridView的实现

    这主要通过使用JavaScript库(MicrosoftAjax.js)和服务器端的ASP.NET AJAX扩展来实现。 GridView控件是ASP.NET中的一个核心组件,用于显示和操作表格数据。它能够自动绑定到各种数据源,如SQL数据库、XML文件或...

    asp.net中Ajax

    在ASP.NET环境中,Ajax(Asynchronous JavaScript and XML)技术被广泛用于实现网页的无刷新更新,从而提升用户体验。Ajax的核心是通过JavaScript与服务器进行异步通信,能够在不重新加载整个页面的情况下更新部分...

    使用asp.net与长连接技术制作网页聊天工具(初步)

    在本文中,我们将深入探讨如何使用ASP.NET与长连接技术来构建一个初步的网页聊天工具。这个话题对于学习C#和.NET框架的开发者来说是非常有价值的,因为它涉及到实时通信和网络编程的重要概念。 首先,让我们理解...

    省市县三级联动asp.net+ajax

    在IT行业中,"省市县三级联动asp.net+ajax"是一个常见的功能需求,特别是在网页表单设计时,用于实现用户选择省、市、县(区)的交互过程。这个功能允许用户在选择省份后,市、县(区)的下拉菜单会自动更新,无需...

    .net JSON+ajax 分页

    标题中的".net JSON+ajax 分页"涉及到的是在.NET框架下,使用JSON数据格式和Ajax技术来实现网页的动态分页功能。这是一个常见的Web开发需求,尤其是在数据量较大的情况下,为了提高用户体验,避免每次操作都刷新整个...

    Asp.Net Ajax无刷新聊天室

    在Asp.Net环境中,这通常通过使用Microsoft的Atlas框架(后来发展为Asp.Net AJAX)实现。Atlas提供了丰富的客户端脚本库和服务器端控件,使得开发者能够轻松地创建具有动态、交互功能的Web应用程序。 在构建Asp.Net...

    asp.net ajax 简单实例

    本实例将展示如何使用 ASP.NET AJAX 进行简单的数据验证操作。具体来说,我们将实现一个简单的用户名检查功能:当用户输入用户名并点击按钮时,会触发服务器端的一个处理程序来检查该用户名是否已被占用,并返回相应...

Global site tag (gtag.js) - Google Analytics