使用 .NET 实现 Ajax 长连接 (Part 1 - Comet Web Service)

http://www.cnblogs.com/cathsfz/archive/2008/02/26/1082668.html


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的接口暴露出来。

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




  1. #1楼[楼主] Cat Chen      2008-02-26 17:59
    貌似Office 2008没有为Mac装上雅黑,结果我的雅黑标题在Mac下也不是雅黑的。
      回复  引用  查看   
  2. #2楼 Jeffrey Zhao      2008-02-26 20:45
    不错不错,加油加油
      回复  引用  查看   
  3. #3楼[楼主] Cat Chen      2008-02-26 21:59
    @Jeffrey Zhao
    谢谢!我很久没写什么了,有些项目长时间没总结了,现在该写下来了。
      回复  引用  查看   
  4. #4楼 m[未注册用户]2008-02-27 17:51
    正关注这方面的东西,谢谢LZ
      回复  引用   
  5. #5楼 助燃      2008-02-27 20:36
    受教了,谢谢楼主辛勤劳动~
      回复  引用  查看   
  6. #6楼 助燃      2008-02-27 22:14
    另外,看了那篇《Comet:基于 HTTP 长连接的“服务器推”技术》,有一个地方不明白,想请教楼主。在基于流方式的服务器推模型这节中,iframe到底充当了怎样的角色,为什么其能够在一次连接中源源不断地向客户端推回数据而自身无需关闭?而且文章中所谓“iframe 服务器端并不返回直接显示在页面的数据,而是返回对客户端 Javascript 函数的调用”又怎么解释?谢谢!
      回复  引用  查看   
  7. #7楼[楼主] Cat Chen      2008-02-28 00:25
    @助燃
    IFrame的作用就和XMLHttpRequest相当。如果在写入IFrame的页面中输出这样一段:
    <script type="text/javascript">alert("hello")</script>
    那么这就是在IFrame内执行脚本,而非返回数据了。当然,要返回数据,只要直接调用父页面的函数,例如:
    <script type="text/javascript">update("hello")</script>
    那么父页面的update()函数就得到了"hello"这个更新消息。
      回复  引用  查看   
  8. #8楼 Intermapper[未注册用户]2008-02-28 12:10
    不错,学习了!
      回复  引用   
  9. #9楼 助燃      2008-02-28 13:09
    --引用--------------------------------------------------
    Cat Chen: @助燃
    IFrame的作用就和XMLHttpRequest相当。如果在写入IFrame的页面中输出这样一段:
    <script type="text/javascript">alert("hello")</script>
    那么这就是在IFrame内执行脚本,而非返回数据了。当然,要返回数据,只要直接调用父页面的函数,例如:
    <script type="text/javascript">update("hello")</script>
    那么父页面的update()函数就得到了"hello"这个更新消息。
    --------------------------------------------------------
    那么也就是说服务器端返回的html就是一系列以script标签扩起来的js语句?而之所以能做到不断返回数据,是让这个html没有加载完毕,服务器端一旦有数据需要向客户端发送,便发送一段js语句,直到超时为止?
    我的疑问主要是服务器端是如何做到让客户端显示没有加载完成,而能够随时向客户端发送数据的。谢谢!
      回复  引用  查看   
  10. #10楼 助燃      2008-02-28 13:12
    另,回复里面的<之类的符号居然没被转义,幸好点引用的时候还是能看到的,是不是该向园子里的领导反应一下。。。
      回复  引用  查看   
  11. #11楼 dudu      2008-02-28 13:24
    @助燃
    发现这个问题,我们会尽快处理。
      回复  引用  查看   
  12. #12楼 dudu      2008-02-28 13:28
    @助燃
    麻烦你删除一下alert代码。
      回复  引用  查看   
  13. #13楼[楼主] Cat Chen      2008-02-28 13:28
    @助燃
    是的,因为只要是一个完整的<script></script>标签,接受后就能够解释执行,无需等待整个页面加载完毕。
      回复  引用  查看   
  14. #14楼 助燃      2008-02-28 14:07
    @dudu
    改好了,不过我发现,点引用的时候,敏感字(如script标签前后的尖括号)显示的是转义后的符号,而不是原文。。。
      回复  引用  查看   
  15. #15楼[楼主] Cat Chen      2008-02-28 14:13
    @助燃
    我刚刚已经把我发的内容都转义了。
      回复  引用  查看   
  16. #16楼 助燃      2008-02-28 14:17
    @Cat Chen
    怪不得,原来是您手工转义的,我还在想为什么只有敏感字附近的尖括号被转义了,而我回复里的单个尖括号却没有被转义。。。
      回复  引用  查看   
  17. #17楼 助燃      2008-02-28 14:21
    --引用--------------------------------------------------
    Cat Chen: @助燃
    是的,因为只要是一个完整的<script></script>标签,接受后就能够解释执行,无需等待整个页面加载完毕。
    --------------------------------------------------------
    嗯,这个我知道,可服务器端是如何做到让客户端显示没有加载完成,而能够随时向客户端发送数据的呢?谢谢!
      回复  引用  查看   
  18. #18楼[楼主] Cat Chen      2008-02-28 14:58
    @助燃
    我已经说了啊,IFrame里面的页面可以这样执行:
    <script type="text/javascript">window.top.update("hello")<script>
    在父窗口中的update()函数就将接受到的参数显示出来,这不就行了?
      回复  引用  查看   
  19. #19楼 助燃      2008-02-28 16:47
    --引用--------------------------------------------------
    Cat Chen: @助燃
    我已经说了啊,IFrame里面的页面可以这样执行:
    <script type="text/javascript">window.top.update("hello")<script>
    在父窗口中的update()函数就将接受到的参数显示出来,这不就行了?
    --------------------------------------------------------
    抱歉,看来是我表述得不够清楚,我不是不明白怎么使用发到客户端的js来控制表现层,而是想知道服务器怎样陆续向客户端发送html而无需结束请求,应该是http协议里的内容吧,实在不好意思,还烦请您指教!谢谢!
      回复  引用  查看   
  20. #20楼 vicqqq[未注册用户]2008-02-28 17:36
    http连接超过两个怎么办,会发生阻塞
      回复  引用   
  21. #21楼 助燃      2008-02-28 19:26
    --引用--------------------------------------------------
    vicqqq: http连接超过两个怎么办,会发生阻塞
    --------------------------------------------------------
    《Comet:基于 HTTP 长连接的“服务器推”技术》那篇文章里有说要避免这种情况发生
      回复  引用  查看   
  22. #22楼[楼主] Cat Chen      2008-02-28 20:07
    @助燃
    对于ASP.NET Web Service来说,只要你不执行完,IIS会尽其所能保持连接,直到:1.ASP.NET超时;2.IIS超时;3.代理或客户端超时。
      回复  引用  查看   
  23. #23楼 vicqqq[未注册用户]2008-02-29 11:31
    --引用--------------------------------------------------
    助燃: --引用--------------------------------------------------
    vicqqq: http连接超过两个怎么办,会发生阻塞
    --------------------------------------------------------
    《Comet:基于 HTTP 长连接的“服务器推”技术》那篇文章里有说要避免这种情况发生
    --------------------------------------------------------
    我想知道怎么去避免,客户端连续刷新就会发生了
      回复  引用   
  24. #24楼 IT_FINANCE[未注册用户]2008-03-03 17:32
    --引用--------------------------------------------------
    Cat Chen: @助燃
    对于ASP.NET Web Service来说,只要你不执行完,IIS会尽其所能保持连接,直到:1.ASP.NET超时;2.IIS超时;3.代理或客户端超时。
    --------------------------------------------------------
    请问
    “只要你不执行完”
    如何才能不执行完?有没代码示例
      回复  引用   
  25. #25楼 助燃      2008-03-03 19:27
    --引用--------------------------------------------------
    IT_FINANCE: --引用--------------------------------------------------
    Cat Chen: @助燃
    对于ASP.NET Web Service来说,只要你不执行完,IIS会尽其所能保持连接,直到:1.ASP.NET超时;2.IIS超时;3.代理或客户端超时。
    --------------------------------------------------------
    请问
    “只要你不执行完”
    如何才能不执行完?有没代码示例
    --------------------------------------------------------
    大概是flush而不close?
      回复  引用  查看   
  26. #26楼[楼主] Cat Chen      2008-03-03 20:46
    @助燃
    在执行到函数return之前都不close啊。例如:
    void SleepDemo()
    {
    Response.Write(DateTime.Now.ToString());
    System.Threading.Thread.Sleep(3000);
    Response.Write(DateTime.Now.ToString());
    }
      回复  引用  查看   
  27. #27楼 助燃      2008-03-03 20:57
    @Cat Chen
    也就是说不需要close了咯?还有什么地方需要close么?比如超时了之后。超时之后应该会自动close吧?谢谢!
      回复  引用  查看   
  28. #28楼[楼主] Cat Chen      2008-03-03 22:57
    @助燃
    不需要,那是IIS的工作。IIS调用你的应用程序,在你的应用程序执行完之后它继续执行,因此断开连接是IIS的逻辑,而不是你需要关注的。
      回复  引用  查看   
  29. #29楼 助燃      2008-03-04 10:41
    @Cat Chen
    再次感谢楼主耐心回复:)
      回复  引用  查看   
  30. #30楼 IT_FINANCE[未注册用户]2008-03-04 11:35
    --引用--------------------------------------------------
    助燃: @Cat Chen
    再次感谢楼主耐心回复:)
    --------------------------------------------------------
    同感谢,期待下文
      回复  引用   
  31. #31楼 welshem[未注册用户]2008-03-04 14:07
    有没有示例啊?正在研究这个技术。谢谢了!
      回复  引用   
  32. #32楼 welshem[未注册用户]2008-03-05 09:13
    急需!快帮帮忙!示例
      回复  引用   
  33. #33楼[楼主] Cat Chen      2008-03-05 09:54
    @welshem
    具体实例将在下次的文章中讨论。
      回复  引用  查看   
  34. #34楼 Qinglong      2009-03-08 11:36
    能否给个具体的Example,好想使用iframe是 remote script,很早就有人用过,能不能提供一个长连接的asp.net的例子?
      回复  引用  查看   
  35. #35楼 小懒人      2009-03-26 17:41
    正在做的web即时通讯就是用的是ajax的长连接模型,请教一下,iis支持的长连接数量是多少?目前遇到一个问题:如果将webim单独部署运行起来是没有问题的,但是如果作为子程序和别的系统集成(公司原有的办公平台),那么就会出现有一个链接阻塞的情况。网上有说iis只支持2个长连接,那么ajax那里会占用一个链接,其他的数据请求会占用另外一个链,现在还无法断定是否办公平台会占用一个链接(从平台登录后会跳转页面,所以应该不会),另外就是如果同时上传文件的话,2个链接都会被占用掉,那么再操作办公平台应该就会一直阻塞,直到文件上传完毕。不知道园子里有没有人遇到这种情况?还请大牛们费心解答一下。谢!
      回复  引用  查看   
  36. #36楼[楼主] Cat Chen      2009-03-26 17:56
    @小懒人
    你把webim在页面上嵌入到现有系统中,但实际的Web服务器仍然是可以分开的。IIS限制长连接应该是指keep-alive,而不是长期不返回的连接。
      回复  引用  查看   
  37. #37楼 小懒人      2009-03-27 10:13

    --引用--------------------------------------------------
    Cat Chen: @小懒人
    你把webim在页面上嵌入到现有系统中,但实际的Web服务器仍然是可以分开的。IIS限制长连接应该是指keep-alive,而不是长期不返回的连接。
    --------------------------------------------------------
    呵呵~ 如果服务器不分开,那么这种情况可能是由于哪些原因造成的呢?博主可不可以留下邮箱或者im?沟通一下?
      回复  引用  查看   
  38. #38楼[楼主] Cat Chen      2009-03-27 10:38
    @小懒人
    你可以在左侧栏找到链接,里面可以看到我的联系方式。
      回复  引用  查看   
  39. #39楼 小懒人      2009-04-01 09:56
    还是没有找到联系方式啊...呵呵...再请教一下博主ajax长轮询的过程中只要使用了session就会出现以前提到的问题,不清楚可能引起这种情况的原因,还请多指教指教,谢谢..
      回复  引用  查看   
  40. #40楼[楼主] Cat Chen      2009-04-01 10:44
    @小懒人
    你是部署在什么版本的IIS上面?
      回复  引用  查看   
  41. #41楼 小懒人      2009-04-01 10:48
    iis 5
      回复  引用  查看   
  42. #42楼[楼主] Cat Chen      2009-04-01 11:07
    @小懒人
    Windows 2000 + IIS5?这不应该有这样的限制啊,除非你在站点上作了配置。
      回复  引用  查看   
  43. #43楼 小懒人      2009-04-01 11:15
    目前是在xp+iis5上,但是到server 2003+iis6上也有同样的问题... 更为夸张的是如果在项目中加入Global.asax(此时代码中还未使用Session)也会产生同样的问题...
      回复  引用  查看   
  44. #44楼 Cat Chen2009-04-01 13:18
    @小懒人
    你早说是XP嘛!我当初就猜你是不是XP,不过你说用来做服务器,我想怎样都不至于是用非Server的Windows吧。

    XP的IIS5.1就是给个人开发用的,限制10个并发连接,没有办法突破,否则要用Server的人都不买Server买家用版Windows了。
      回复  引用   
  45. #45楼 dunnice      2009-09-19 19:35
    问个问题:
    在使用ACK的机制下,如果客户端没有收到数据,也就不会发送ACK,这个时候,服务器端如何处理, 上面好像没有说到, 是不是默认超时重发, 不知我的理解是否正确?
      回复  引用  查看   
  46. #46楼[楼主] Cat Chen      2009-09-19 22:13
    @dunnice
    HTTP是可靠连接,在HTTP没有意外断开的情况下,服务器端发送的数据都能够送达。如果HTTP连接以外断开了,客户端会重新发起HTTP连接,使用的仍旧是上一次连接的ACK,服务器端就知道上一次的发送没有成功了。
      回复  引用  查看   
  47. #47楼 lexus      2010-02-17 09:53
    请教一下,长链接这个功能对host的server容器有什么特别要求吗?还是只有特定的容器长能应用这项技术?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值