阿不

在腾讯微博和新浪微博 @hjf1223 ,最新的技术观点在那

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  211 随笔 :: 0 文章 :: 2998 评论 :: 75 引用

Http环境本身是一种无连接状态的架构,在这种架构下服务器只能是被动的接受客户端的请求,返回结果,而无法主动的给客户端发送数据。而在很多需要实时数据交互(比如Web IM)的场景中,我们却希望能及时得到服务器给我们返回的数据。此时,一种最为普遍的做法是:在客户端用定时器,定时去请求服务器的服务,来得到最新数据。而这样一来,很多时候却是在做无用功,频繁的请求也会无端的增加服务器和客户端在请求Web服务上的消耗。那么是否有一种更好的办法,既可以及时得到服务器的返回,同时又可以减少做无用功,以及频繁请求带来的性能问题呢?

记得前不久,在园子里有这样的一篇文章,介绍了几种WEB环境定时刷新数据的机制。其中就有提到google gmail的一种比较巧妙的做法,现在记不得当时是怎么理解这种做法了,只记得有“保持长连接”的基本做法。(当然现在也找不到这篇文章了,希望了解的朋友能提醒一下)。今天由于架构方案的需要,再来仔细思考连接保持方案,以及参考gmail的请求行为,总结了一下,应该是这样的:客户端一直保持一个与服务器的连接,这个连接一直保持着对服务器的请求动作,直到服务器发现有数据后给它返回后,才结束返回这一次请求。客户端在接收到请求返回后,在处理这些返回之前,又向服务器发送了一次连接请求,直到下一次有数据返回。不可避免的有一种情况,就是如果服务器长时间没有需要给客户端发送数据的话,那么可以就会造成请求失败(超时或其它原因)。对于这种情况的处理也是一样的,在错误的回调事件中重新发送一次请求连接。这样就可以模拟保持连接状态了。

用伪代码来描述一下思路吧:

客户端脚本:

   1: function Request()
   2: {
   3:     Ajax.Request(url,OnSuccessed,OnFailed);
   4: }
   5: function OnSuccessed(response)
   6: {
   7:     //重新发送一次请求
   8:     Request();
   9:      //处理返回数据
  10: }
  11: function OnFailed()
  12: {
  13:     //错误(超时)重新请求
  14:     Request();
  15: }

Web服务:

   1: public class IMService : IHttpHandler
   2: {
   3:     public bool IsReusable{return false;}
   4:     public void ProcessRequest(HttpContext context)
   5:     {
   6:         //读取最新数据
   7:         while(true)
   8:         {
   9:             string message = GetMessage();
  10:             if(!string.IsNullOrEmpty(message))
  11:             {
  12:                 context.Response.Write(message);
  13:                 break;
  14:             }
  15:             Thread.Sleep(500);//等待一段时间再重新读取。
  16:         }
  17:     }
  18:     private string GetMessage()
  19:     {
  20:         //取得最新数据
  21:     }
  22: }

这种方案的好处有:客户端可以第一时间得到服务器需要给客户端发送的数据(而至于Web服务怎么知道要给客户端发送数据,也就是服务器的轮循设计,则是另一个需要考虑的方案);可以减化客户端逻辑,无需要创建和释放定时器,并减小由此产生的对客户端性能的损失;减少去服务器的请求次数,减少做无用功,节约节省带宽和减少服务器资源需要处理的连接请求。

相信在此之前,已经有很多人在使用这种方案了。欢迎大家就此方案发表自己的见解。

补充:服务器部分的设计,除了使用轮循外,也可以考虑使用资源互斥访问的方式来设计,这样做可以获得更佳性能,更高实时性,具体的方案应当根据实际情况来考虑。相关的设计可以参考Cat Chen的文章《使用 .NET 实现 Ajax 长连接 (Part 2 - Mutex Wait & Signal)

阿不 http://hjf1223.cnblogs.com
posted on 2008-04-10 20:11 阿不 阅读(6229) 评论(35) 编辑 收藏

评论

#1楼 2008-04-10 20:25 RicCC      
http://www.ibm.com/developerworks/cn/web/wa-lo-comet/
 回复 引用 查看   

#2楼 2008-04-10 20:26 李战      
在长连接中,客户端发出的异步请求得有个超时吧。还有就是服务器端最好别用Sleep,只顾睡觉的话有啥事都不知道了。一般是让当前请求线程等待某个信号,有信号了,就立即读取并返回客户端啊。当然也不能傻等哈。而客户端收到读取数据后,立马又发出长连接。数据最好成批读取,能读的通通读光,免得往返多次耽误时间。

数据的顺序要由客户端自己维护哟。

俺瞎说的,错了可以怪我。
 回复 引用 查看   

#3楼[楼主] 2008-04-10 20:33 阿不      
@RicCC
谢谢,确实非常需要这篇文章。
@李战
上面只是自己从经验的角度出发推想出来的,难免还会有很多需要解决的问题而没有考虑到,比如Http对同一个域名的同时连接限制等等。
而对于服务器端的设计,我想,这个也是需要再考虑和衡量的。我上面只是做了一个简单的演未例子。纯粹只是方便演示之用
 回复 引用 查看   

#4楼 2008-04-10 20:59 l22[未注册用户]
这种技术需要服务器端的特殊调整,普通的web服务器实现代话性能会是问题
 回复 引用   

#5楼 2008-04-10 21:23 怪怪      
我觉得要保持长连接, 关键是服务器硬件得够强..。

@RicCC
居然是个女孩写的....
 回复 引用 查看   

#6楼 2008-04-10 21:31 Cat Chen      
等待500ms其实还是pull的形式来模拟push,你可以改用Mutex来实现真正的push,参考:
http://www.cnblogs.com/cathsfz/archive/2008/03/08/1096654.html
 回复 引用 查看   

#7楼 2008-04-10 22:46 wingoo      
刚看到篇文章
http://erlang-china.org/misc/practical_think_on_rest.html
 回复 引用 查看   

#8楼 2008-04-10 23:51 RicCC      
@怪怪
恩这个行业里尤其是搞技术的是凤毛麟角了
 回复 引用 查看   

#9楼 2008-04-11 06:44 曾哲      
写得不错
 回复 引用 查看   

#10楼 2008-04-11 09:35 zdleek[未注册用户]
我在奇怪 @怪怪 怎么看出楼主是女的,我却看不出
 回复 引用   

#11楼[楼主] 2008-04-11 09:54 阿不      
@zdleek
@怪怪 没说我是女的,它是说@RicCC提供的文章是女的写的。:(
 回复 引用 查看   

#12楼 2008-04-11 10:39 zdleek[未注册用户]
@阿不
原来如此,我搞错意思了
 回复 引用   

#13楼 2008-04-11 10:58 jackyz[未注册用户]
the google way, also check this:
http://erlang-china.org/misc/server-push-the-google-way.html
 回复 引用   

#14楼 2008-04-11 11:09 队长      
“很多时候却是在做无用功,频繁的请求也会无端的增加服务器和客户端在请求Web服务上的消耗。那么是否有一种更好的办法,既可以及时得到服务器的返回,同时又可以减少做无用功,以及频繁请求带来的性能问题呢?”

博主的这种方式实际上是不停地对服务器发出请求了,对服务器端的压力会不会更大呢?

不过,这个方法真的不错,很巧妙!
谢谢分享!

 回复 引用 查看   

#15楼 2008-04-11 11:15 witluo[未注册用户]
这种占用web服务器的连接数, 影响web的吞吐率吧
 回复 引用   

#16楼[楼主] 2008-04-11 11:18 阿不      
@ jackyz
thanks
 回复 引用 查看   

#17楼[楼主] 2008-04-11 11:19 阿不      
@队长
@witluo
当连接数量达到一程度的话,肯定会引发相关的问题。
但是相对于数据的实时性,客户端性能花费与轮循相比还有很大优势的。
 回复 引用 查看   

#18楼[楼主] 2008-04-11 11:23 阿不      
@Cat Chen
你的这篇文章给了我很大的启发。
长连接请求的方式,确实我们应该把优化的重点放在服务器端的设计上。如果服务器使用轮循的方式一样也是会造成很多资源浪费。而我们如果使用资源互斥的做法,可以让请求数据一端等待通知即可,而不必定时去查询。大大提高性能的同时,又可以大大提高实时性。
 回复 引用 查看   

#19楼 2008-04-11 12:49 yzlhccdec      
Bidirectional-streams Over Synchronous HTTP
 回复 引用 查看   

只能做test用,不能真正投入生产,iis做不了,这种思路在6和7上我都试过了. 不是完全在于硬件问题
 回复 引用 查看   

#21楼[楼主] 2008-04-11 13:34 阿不      
@new 维生素C.net()
那还有哪些更多的限制呢?
如果说连接数有限的情况下能否成功呢?
 回复 引用 查看   

好文章啊~~
学到不少东东
 回复 引用   

#23楼 2008-04-11 14:54 Rivers Zhao      
好文章,顶一个.
 回复 引用 查看   

#24楼 2008-04-11 15:47 Jeffrey Zhao      
comet其实需要特殊的web服务器,iis并不适合……
 回复 引用 查看   

#25楼 2008-04-11 17:00 hut[未注册用户]
jetty提供了实现了comet的web容器。
 回复 引用   

#26楼[楼主] 2008-04-11 17:08 阿不      
@Jeffrey Zhao
@hut
@new 维生素C.net()
确实,IIS在并发连接数上有很大的限制。我先尝试是否能满足要求吧。
 回复 引用 查看   

#27楼 2008-04-12 09:59 gogoyc[未注册用户]
while(true) 这样asp.net只能处理两个请求哦,后面的都堵塞了。你试了代码的没有哦?
 回复 引用   

不错,这里有个开源代码,抛砖引玉。

http://www.svnhost.cn/Project/Detail-1.shtml
 回复 引用   

#29楼 2008-04-12 11:08 Yong Zhang      
不合适!最起码也要开多线程来做!
 回复 引用 查看   

#30楼[楼主] 2008-04-12 11:15 阿不      
@gogoyc
上面只是一个演示。。
 回复 引用 查看   

#31楼[楼主] 2008-04-12 11:16 阿不      
@Yong Zhang
你再去了解一下,Web环境下是如何来处理客户端请求的。
 回复 引用 查看   

#32楼 2008-04-12 12:38 李战      
@Jeffrey Zhao
先握手。
俺还以为IIS最适合拉住彗星的长尾巴呢,专家这么一说,俺就担心了。因为,俺正打算在IIS上搞这玩意儿。

俺原来以为IIS的完成端口模型,可以支持大量的并发网络I/O读写,为大量的HTTP长连接准备了基础。

俺还想好给彗星的长尾巴提供一些专门的域名,怕浏览器只准长出两只尾巴这个龟腚。而且,IIS中肯定选择保持连接,并把连接超时设长一点,也不限制并发连接数。

俺还考虑到ASP.NET应用程序域或进程可能会被重启,或者在Web园甚至Web场的情况下的复杂情况下,必须设计有效的机制来保证彗星关心的事件或消息是持续的。有点复杂

因为一切都还没正式测试过,专家随口一句话,俺的自信一下子就没有了啊。

能否多吐几个字啊?

 回复 引用 查看   

#33楼 2008-04-12 13:23 S.Sams      
其实就是模拟客户端不间断访问.这是Web的特征.
 回复 引用 查看   

#34楼 2008-04-13 10:50 Share赖      
再看了一个.NET实现长连接,有点收获,现写出来,看看理解对不?


拿WEB IM来作例子吧

B说“HI”给S,S如果没有回答,那么一直SLEEP,不返回
B看到S没回答,抛弃上次的HI请求,再HI一次过去,这次S回答了,马上回答返回,不SLEEP了
B得到返回后的数据,马上再发过个请求过去,然后再处理数据,接着就是等S还有没回话,如果没有,这个请求一直等着,直到S回话,唤醒线程,返回,如果一直不回话,两者一直干等,S有个等待时长,超过就抛弃。
B等不耐烦了,再发请求,抛弃上次请求。
总过程就这样发请求,抛弃请求
呵呵,语言整理有点问题,大家见笑了。
 回复 引用 查看   

#35楼 2008-04-14 12:42 孤野山风      
@Share赖
最少我是这样处理的,所以我支持 嘿嘿
 回复 引用 查看