我如何实现基本的“长轮询”?
我可以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的)…但是作为一个简单的例子,Apache已经足够了! 这个脚本可以很容易地用任何语言编写(我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个类(见下文)。
- 控制器 :处理创build有效响应所需的操作(数据库操作等)
- 处理器 :pipe理与网页(本身)的asynchronous通信
- IAsynchProcessor :服务处理实现这个接口的实例
- 服务 :处理实现IAsynchProcessor的请求对象
- 请求 :包含您的响应(对象)的IAsynchProcessor包装器
- 响应 :包含自定义对象或字段
我用这个来处理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 ';
这里是一个演示:
以下是我为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等。