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

使用 .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。同时我们也看到了没有消息队列造成的问题,因此确定接下来我们要做一个消息队列。

分享到:
评论

相关推荐

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

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

    在Asp.net下实现变长连接的web即时应用的实现范例及ReverseAjax的演示介绍

    ASP.NET实现变长连接需要使用到异步页面的技术,同时该页面需要禁用会话来实现每次请求都产生一个新的实例执行。 代码如下: <%@ Page Language=”C#” AutoEventWireup=”true” CodeFile=”response.aspx....

    Asp.net+jQuery实现全Ajax的电子商城

    该项目完全抛弃传统的Table布局,全部采用先进的Css+Div,画面绚丽,手写代码实现Ajax效果,没有.net Ajax框架的bug,结合动画提高了用户的体验,因该项目系委托开发,仅能公开部分代码,望见谅。使用时请先运行App_...

    asp.net 实现服务器推功能

    asp.net实现的服务器推功能 写的ajax实时聊天室

    使用Jquery.Pagination.js+ASP.NET实现无刷新分页

    此源码使用了Jquery.Pagination.js+Jquery.Ajax+Asp.net技术实现了无刷新分页的效果。其中用到的Asp.net知识有Linq to sql和ashx技术。分页插件可以自定义样式,自定义的样式在pagenumstyle.css文件中。项目使用vs...

    ASP.NET实现comet的聊天交互功能

    Google一下,可以知道ASP.NET实现异步处理的话,实现IHttpAsyncHandler接口就可以了。该接口的BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)方法返回一个IAsyncResult对象。当不...

    页面间隔半秒钟更新时间 Asp.net使用Comet开发http长连接示例分享

    Comet(Reverse AJAX)主要是通过HTTP长连接, 保持和服务器的连接,实现Server PUSH 和双向通信,下面通过示例学习他的使用方法

    ajax .net jquery 增删改查 ajax模糊查询

    用的是.net三层,连接语句在DBHelper里面自行修改 实现了简易的增删改查 和ajax模糊查询 数据库是sql2000的 在DB里面 希望可以帮到刚接触ajax的人

    asp.net知识库

    在ASP.NET中使用WINDOWS验证方式连接SQL SERVER数据库 改进ADO.Net数据库访问方式 ASP.NET 2.0 绑定高级技巧 简单实用的DataSet更新数据库的类+总结 [ADO.NET]由数据库触发器引发的问题 为ASP.NET封装的SQL数据库...

    利用ajax实现注册页面 asp.net技术(C#) 用visual studio 2005 + sqlserver 2000 实现 包含数据库和源代码

    此为利用ajax技术实现的一个简单注册,利用的是c# asp.net技术 坏境用的是visual studio 2005 + SqlServer 2000 包含数据库及源代码 希望对大家有帮助

    .Net MVC实现长轮询

     客户端向服务器发送Ajax请求,服务器接收到请求后,保持连接不返回消息,直到进行相关处理完毕后才返回响应信息并关闭连接,客户端接收到响应信息后,进行相关处理,处理完毕后再想服务器发送新的请求。...

    ASP.NET实现WEB聊天程序(无需证书,数据库)

    用了些AJAX,没有用到数据库,使用了TCP/IP短连接技术,支持SOCKET,先启动服务器,然后开两个网页就可以看效果了。客户端是WEB程序。可实现客户端与客户端之间的通讯。 服务器用的控制台写的。

    ASP.NET MVC网上图书商城电商网站运营源码

    2项目使用 ASP.NET MVC框架,使用ADO.NET实体数据模型 EF连接数据库,view视图用Razor视图,控制器动作方法使用 Linq to sql 实现与数据库进行数据交互,并完成业务逻辑操作 3页面使用伪静态处理 网站前台 1Web前端...

    C#服务器推技术(http长连接)的应用实例

    2.不再定时不断请求Http请求,降低服务器资源消耗,可参考Taobao核心系统团队博客《http长连接200万尝试及调优》; 3.TX微博等很多应用均采用服务器推技术,你还在用Ajax? 网上关于服务器推技术的文章多,但关于.net...

    .NET CORE MVC 3.1 增、删、查、改 分页的 简单用例

    .NET CORE 分页 增删查改,简单 容易 一学就会 , 只要修改连接字符串用 户名 密 码 直接能运行DEMO

    ASP.NET3.5从入门到精通

    10.5.2 使用OLE DB .NET Data Provider 连接txt 10.5.3 使用System.IO 命名空间 10.6 访问SQLite 10.6.1 SQLite 简介 10.6.2 SQLite 连接方法 10.7 小结 第四篇 ASP.NET网络编程 第 11 章用户控件和自定义控件 11.1 ...

    亮剑.NET深入体验与实战精要2

    5.5.3 ASP.NET连接Oracle失败的解决方法 245 本章常见技术面试题 246 常见面试技巧之经典问题巧回答 246 本章小结 247 第6章 关于XML 249 6.1 XML概述 250 6.1.1 XML和HTML有什么区别 250 6.1.2 XML的优势 250 6.2 ...

    ASP.NET 3.5 开发大全

    10.5.2 使用OLE DB .NET Data Provider 连接txt 10.5.3 使用System.IO命名空间 10.6 访问SQLite 10.6.1 SQLite简介 10.6.2 SQLite连接方法 10.7 小结 第四篇 ASP.NET网络编程 第11章 用户控件和自定义控件 11.1 用户...

    ASP.NET 3.5 开发大全word课件

    10.5.2 使用OLE DB .NET Data Provider 连接txt 10.5.3 使用System.IO命名空间 10.6 访问SQLite 10.6.1 SQLite简介 10.6.2 SQLite连接方法 10.7 小结 第四篇 ASP.NET网络编程 第11章 用户控件和自定义控件 11.1 用户...

    Ajax无刷新整合型网站论坛一体化开源框架

    3.采用Anthem.net实现ajax效果,无需依赖ASP.NET AJAX的任何库文件。 4.用户注册登录全部采用ASP.NET2.0内置的现成控件实现。 5.网站菜单分类请修改数据库中的Catalog及Class,支持二级主题分类。 6.Catalog表中的...

Global site tag (gtag.js) - Google Analytics