我如何实现基本的“长轮询”?

我可以find很多关于长轮询如何工作的信息(例如, 这个和这个 ),但是没有关于如何在代码中实现的简单例子。

我所能find的是cometd ,它依赖于Dojo JS框架和相当复杂的服务器系统。

基本上,我将如何使用Apache来服务请求,以及如何编写一个简单的脚本(比如说用PHP)来“长时间轮询”服务器以获得新消息?

这个例子不必是可扩展的,安全的或完整的,只需要工作!

这比我最初想象的要简单。基本上你有一个什么都不做的页面,直到你想发送的数据可用(比如说一个新的消息到达)。

这是一个非常基本的例子,它在2-10秒后发送一个简单的string。 三分之一的机会返回错误404(在即将到来的Javascript示例中显示error handling)

msgsrv.php

 <?php if(rand(1,3) == 1){ /* Fake an error */ header("HTTP/1.0 404 Not Found"); die(); } /* Send a string after a random number of seconds (2-10) */ sleep(rand(2,10)); echo("Hi! Have a random number: " . rand(1,10)); ?> 

注意:对于一个真实的网站,在像Apache这样的普通networking服务器上运行它会迅速捆绑所有“工作线程”,并使其无法响应其他请求。有办法解决这个问题,但build议写一个类似Python的扭曲的“长轮询服务器”,它不依赖于每个请求的一个线程。 cometD是一个stream行的(有几种语言), Tornado是专门为这些任务而制作的新框架(它是为FriendFeed的长轮询代码而构build的)…但是作为一个简单的例子,A​​pache已经足够了! 这个脚本可以很容易地用任何语言编写(我select了Apache / PHP,因为它们很常见,而且我正好在本地运行它们)

然后,在Javascript中,您请求上述文件( msg_srv.php ),然后等待响应。 当你得到一个,你的数据行事。 然后你请求文件并再次等待,根据数据(重复)

接下来是这样一个页面的例子。当页面被加载时,它发送msgsrv.php文件的初始请求。如果成功,我们将消息附加到#messages div,然后在1秒钟之后,我们呼叫waitForMsg函数再次触发等待。

1秒setTimeout()是一个真正的基本速率限制器,它没有这个工作正常,但如果msgsrv.php 总是立即返回(例如语法错误) – 洪水浏览器,它可以很快冻结。 最好检查文件是否包含有效的JSON响应,和/或保持每分钟/每秒的请求总数,并适当地暂停。

如果页面错误,它将错误追加到#messages div,等待15秒,然后再次尝试(等同于我们在每条消息之后等待1秒)

这种方法的好处是非常有弹性。 如果客户端的互联网连接中断,它会超时,然后尝试重新连接 – 这是轮询工作的固有长度,不需要复杂的error handling

无论如何,使用jQuery框架的long_poller.htm代码:

 <html> <head> <title>BargePoller</title> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js" type="text/javascript" charset="utf-8"></script> <style type="text/css" media="screen"> body{ background:#000;color:#fff;font-size:.9em; } .msg{ background:#aaa;padding:.2em; border-bottom:1px #000 solid} .old{ background-color:#246499;} .new{ background-color:#3B9957;} .error{ background-color:#992E36;} </style> <script type="text/javascript" charset="utf-8"> function addmsg(type, msg){ /* Simple helper to add a div. type is the name of a CSS class (old/new/error). msg is the contents of the div */ $("#messages").append( "<div class='msg "+ type +"'>"+ msg +"</div>" ); } function waitForMsg(){ /* This requests the url "msgsrv.php" When it complete (or errors)*/ $.ajax({ type: "GET", url: "msgsrv.php", async: true, /* If set to non-async, browser shows page as "Loading.."*/ cache: false, timeout:50000, /* Timeout in ms */ success: function(data){ /* called when request to barge.php completes */ addmsg("new", data); /* Add response to a .msg div (with the "new" class)*/ setTimeout( waitForMsg, /* Request next message */ 1000 /* ..after 1 seconds */ ); }, error: function(XMLHttpRequest, textStatus, errorThrown){ addmsg("error", textStatus + " (" + errorThrown + ")"); setTimeout( waitForMsg, /* Try again after.. */ 15000); /* milliseconds (15seconds) */ } }); }; $(document).ready(function(){ waitForMsg(); /* Start the inital request */ }); </script> </head> <body> <div id="messages"> <div class="msg old"> BargePoll message requester! </div> </div> </body> </html> 

我有一个非常简单的聊天示例作为slosh的一部分。

编辑 :(因为每个人都在这里粘贴他们的代码)

这是完整的基于JSON的多用户聊天使用长轮询和晃动 。 这是如何进行呼叫的演示 ,所以请忽略XSS问题。 没有人应该首先消毒。

请注意,客户端始终与服务器有连接,只要有人发送消息,每个人都应该立即看到它。

 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <!-- Copyright (c) 2008 Dustin Sallings <dustin+html@spy.net> --> <html lang="en"> <head> <title>slosh chat</title> <script type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script> <link title="Default" rel="stylesheet" media="screen" href="style.css" /> </head> <body> <h1>Welcome to Slosh Chat</h1> <div id="messages"> <div> <span class="from">First!:</span> <span class="msg">Welcome to chat. Please don't hurt each other.</span> </div> </div> <form method="post" action="#"> <div>Nick: <input id='from' type="text" name="from"/></div> <div>Message:</div> <div><textarea id='msg' name="msg"></textarea></div> <div><input type="submit" value="Say it" id="submit"/></div> </form> <script type="text/javascript"> function gotData(json, st) { var msgs=$('#messages'); $.each(json.res, function(idx, p) { var from = p.from[0] var msg = p.msg[0] msgs.append("<div><span class='from'>" + from + ":</span>" + " <span class='msg'>" + msg + "</span></div>"); }); // The jQuery wrapped msgs above does not work here. var msgs=document.getElementById("messages"); msgs.scrollTop = msgs.scrollHeight; } function getNewComments() { $.getJSON('/topics/chat.json', gotData); } $(document).ready(function() { $(document).ajaxStop(getNewComments); $("form").submit(function() { $.post('/topics/chat', $('form').serialize()); return false; }); getNewComments(); }); </script> </body> </html> 

Tornadodevise用于长轮询,在/ examples / chatdemo中包含一个非常小的(几百行Python) 聊天应用程序 ,包括服务器代码和JS客户端代码。 它是这样工作的:

  • 客户端使用JS从(最后一条消息的编号)请求更新,服务器URLHandler接收这些更新并添加一个callback来响应客户端到一个队列。

  • 当服务器收到新消息时,onmessage事件触发,循环遍历callback,并发送消息。

  • 客户端JS收到消息,将其添加到页面,然后要求更新此新的消息ID。

我认为客户端看起来像一个正常的asynchronousAJAX请求,但你期望它需要很长的时间才能回来。

服务器然后看起来像这样。

 while (!hasNewData()) usleep(50); outputNewData(); 

所以,AJAX请求发送到服务器,可能包括上次更新的时间戳,以便hasNewData()知道你已经得到了什么数据。 服务器然后坐在一个循环睡觉,直到新的数据可用。 一直以来,您的AJAX请求仍然连接,只是挂在那里等待数据。 最后,当有新的数据可用时,服务器将它提供给您的AJAX请求并closures连接。

对于如何使用PHP&jQuery进行长时间轮询,这是一个不错的5分钟截屏: http : //screenr.com/SNH

代码与上面dbr的例子非常相似。

这里有一些我在C#中用于长轮询的类。 基本上有6个类(见下文)。

  1. 控制器 :处理创build有效响应所需的操作(数据库操作等)
  2. 处理器 :pipe理与网页(本身)的asynchronous通信
  3. IAsynchProcessor :服务处理实现这个接口的实例
  4. 服务 :处理实现IAsynchProcessor的请求对象
  5. 请求 :包含您的响应(对象)的IAsynchProcessor包装器
  6. 响应 :包含自定义对象或字段

我用这个来处理Comet,我也用Java Glassfish服务器build立了Comet,并且通过订阅cometdaily.com发现了很多其他的例子

这是Erik Dubbelboer使用Content-type: multipart/x-mixed-replace标头在PHP中的一个简单的长轮询示例 :

 <? header('Content-type: multipart/x-mixed-replace; boundary=endofsection'); // Keep in mind that the empty line is important to separate the headers // from the content. echo 'Content-type: text/plain After 5 seconds this will go away and a cat will appear... --endofsection '; flush(); // Don't forget to flush the content to the browser. sleep(5); echo 'Content-type: image/jpg '; $stream = fopen('cat.jpg', 'rb'); fpassthru($stream); fclose($stream); echo ' --endofsection '; 

这里是一个演示:

http://dubbelboer.com/multipart.php

以下是我为Inform8 Web开发的一个长轮询解决scheme。 基本上你重写类并实现loadData方法。 当loadData返回一个值或操作超时时,它将打印结果并返回。

如果您的脚本处理可能需要超过30秒,则可能需要将set_time_limit()调用更改为更长的时间。

Apache 2.0许可证。 github上的最新版本https://github.com/ryanhend/Inform8/blob/master/Inform8-web/src/config/lib/Inform8/longpoll/LongPoller.php

瑞安

 abstract class LongPoller { protected $sleepTime = 5; protected $timeoutTime = 30; function __construct() { } function setTimeout($timeout) { $this->timeoutTime = $timeout; } function setSleep($sleep) { $this->sleepTime = $sleepTime; } public function run() { $data = NULL; $timeout = 0; set_time_limit($this->timeoutTime + $this->sleepTime + 15); //Query database for data while($data == NULL && $timeout < $this->timeoutTime) { $data = $this->loadData(); if($data == NULL){ //No new orders, flush to notify php still alive flush(); //Wait for new Messages sleep($this->sleepTime); $timeout += $this->sleepTime; }else{ echo $data; flush(); } } } protected abstract function loadData(); } 

感谢代码, dbr 。 只是在行的long_poller.htm小错字

 1000 /* ..after 1 seconds */ 

我认为应该是

 "1000"); /* ..after 1 seconds */ 

为它工作。

对于那些有兴趣的人,我尝试了Django等价物。 开始一个新的Django项目,说lp长时间轮询:

 django-admin.py startproject lp 

调用消息服务器的应用程序msgsrv

 python manage.py startapp msgsrv 

将以下行添加到settings.py以具有模板目录:

 import os.path PROJECT_DIR = os.path.dirname(__file__) TEMPLATE_DIRS = ( os.path.join(PROJECT_DIR, 'templates'), ) 

urls.py中定义你的URL模式,如下所示:

 from django.views.generic.simple import direct_to_template from lp.msgsrv.views import retmsg urlpatterns = patterns('', (r'^msgsrv\.php$', retmsg), (r'^long_poller\.htm$', direct_to_template, {'template': 'long_poller.htm'}), ) 

而msgsrv / views.py应该如下所示:

 from random import randint from time import sleep from django.http import HttpResponse, HttpResponseNotFound def retmsg(request): if randint(1,3) == 1: return HttpResponseNotFound('<h1>Page not found</h1>') else: sleep(randint(2,10)) return HttpResponse('Hi! Have a random number: %s' % str(randint(1,10))) 

最后,templates / long_poller.htm应该与上面的错字更正一样。 希望这可以帮助。

看看这个博客文章 ,它有一个简单的聊天应用程序在Python / Django / gevent的代码 。

这是PHP是一个非常糟糕的select的情况之一。 正如前面提到的,你可以很快地把你所有的Apache工作者都捆绑在一起。 PHP是为了启动,执行,停止而构build的。 它不是为开始而build,等待…执行,停止。 你会很快陷入你的服务器,发现你有令人难以置信的扩展问题。

也就是说,你仍然可以用PHP来做到这一点,让它不使用nginx HttpPushStreamModule杀死你的服务器: http ://wiki.nginx.org/HttpPushStreamModule

你在Apache之前设置nginx(或其他),它会照顾保持打开并发连接。 您只需通过将数据发送到内部地址来进行响应,您可以使用后台作业将数据发送到内部地址,或者只要有新请求进入时等待的人就可以启动这些消息。这样可以防止在长时间轮询期间PHP进程处于打开状态。

这不是PHP专有的,可以用nginx和任何后端语言来完成。 并发打开的连接负载等于Node.js,所以最大的好处就是它可以让你脱离NEEDING Node。

你看到很多其他人提到其他语言库来完成长时间投票,这是有道理的。 PHP自然不适合这种行为。

这是一个 jQuery客户端附带的node.js示例 。 还有关于在heroku上设置的说明。

WS-I团队发布了一个名为“可靠的安全configuration文件”的东西,它有一个Glass Fish和.NET实现 ,显然互操作性很好。

运气好的话,还有一个Javascript实现。

还有一个使用HTTP双工的Silverlight实现。 您可以将JavaScript连接到Silverlight对象,以便在发生推送时获得callback。

也有商业付费版本 。

为什么不考虑networking套接字而不是长时间轮询? 它们非常高效,易于安装。 但是,只有在现代浏览器中才支持它们。 这是一个快速参考

对于一个ASP.NET MVC实现,看看NuGet上提供的 SignalR。注意,NuGet经常是来自Git源头的 ,它会非常频繁地提交。

在Scott Hanselman的博客上阅读更多关于SignalR的信息

你可以试试icomet( https://github.com/ideawu/icomet ),一个用libevent构build的C1000K C ++慧星服务器。 icomet还提供了一个JavaScript库,使用起来很简单

 var comet = new iComet({ sign_url: 'http://' + app_host + '/sign?obj=' + obj, sub_url: 'http://' + icomet_host + '/sub', callback: function(msg){ // on server push alert(msg.content); } }); 

icomet支持多种浏览器和操作系统,包括Safari(iOS,Mac),IE(Windows),Firefox,Chrome等。