How do I implement basic “Long Polling”?
我可以找到很多关于轮询工作时间的信息(例如,this和this),但没有简单的代码实现示例。
我所能找到的只是Cometd,它依赖于DojoJS框架和一个相当复杂的服务器系统。
基本上,我将如何使用Apache来服务请求,以及如何编写一个简单的脚本(例如,在PHP中),它将"长时间轮询"服务器以获取新消息?
这个例子不需要是可伸缩的、安全的或完整的,它只需要工作就可以了!
比我最初想象的简单……基本上,您有一个不做任何事情的页面,直到您想要发送的数据可用为止(例如,一条新消息到达)。
下面是一个非常基本的例子,它在2-10秒后发送一个简单的字符串。返回错误404的几率为1/3(在接下来的javascript示例中显示错误处理)
1 2 3 4 5 6 7 8 9 10 11 |
注意:对于一个真正的站点,在像Apache这样的常规Web服务器上运行它将很快绑定所有"工作线程",使其无法响应其他请求。有很多方法可以解决这个问题,但是建议用类似于Python的Twisted的东西编写一个"长轮询服务器",它不依赖于每个请求一个线程。Cometd是一个流行的框架(有多种语言可供选择),Tornado是一个专门为此类任务设计的新框架(它是为FriendFeed的长轮询代码而构建的)。但作为一个简单的例子,Apache已经足够了!这个脚本可以很容易地用任何语言编写(我选择了Apache/PHP,因为它们很常见,而且我碰巧在本地运行它们)。
然后,在javascript中,您请求上述文件(
下面是这样一个页面的例子。加载页面时,发送对
1秒的
如果页面出错,它将错误附加到
这种方法的好处在于它具有很强的弹性。如果客户端的Internet连接断开,它将超时,然后尝试重新连接-这是轮询工作时间的固有特征,不需要复杂的错误处理
总之,使用jquery框架的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | <html> <head> BargePoller <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js" type="text/javascript" charset="utf-8"> <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( ""+ msg +"" ); } 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 */ }); </head> <body> BargePoll message requester! </body> </html> |
我有一个非常简单的聊天例子作为Slosh的一部分。
编辑:(因为每个人都在这里粘贴他们的代码)
这是完整的基于JSON的多用户聊天,使用长轮询和slosh。这是如何进行调用的演示,因此请忽略XSS问题。任何人都不应该在没有先消毒的情况下部署它。
注意,客户机总是与服务器有连接,一旦有人发送消息,每个人都应该立即大致看到它。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | <?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> slosh chat <script type="text/javascript" src="http://code.jquery.com/jquery-latest.js"> <link title="Default" rel="stylesheet" media="screen" href="style.css" /> </head> <body> Welcome to Slosh Chat <span class="from">First!:</span> <span class="msg">Welcome to chat. Please don't hurt each other.</span> <form method="post" action="#"> Nick: <input id='from' type="text" name="from"/> Message: <textarea id='msg' name="msg"></textarea> <input type="submit" value="Say it" id="submit"/> </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("<span class='from'>" + from +":</span>" + " <span class='msg'>" + msg +"</span>"); }); // 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(); }); </body> </html> |
Tornado是为长轮询而设计的,在/examples/chatdemo中包含一个非常小的(几百行python)聊天应用程序,包括服务器代码和JS客户机代码。工作原理如下:
客户机使用JS请求自(最后一条消息的数量)以来的更新,服务器urlhandler接收这些更新,并添加回调以响应客户机对队列的响应。
当服务器收到新消息时,onmessage事件将激发、循环调用并发送消息。
客户端JS接收消息,将其添加到页面,然后请求自这个新消息ID以来的更新。
我认为客户机看起来像一个普通的异步Ajax请求,但您希望它需要"很长时间"才能恢复。
服务器看起来是这样的。
1 2 3 4 |
因此,Ajax请求将发送到服务器,可能包括上次更新的时间戳,以便您的
以下是我在C中用于长轮询的一些类。基本上有6个班(见下文)。
这是一个关于如何使用php&jquery进行长时间轮询的5分钟的精彩截屏:网址:http://screener.com/snh
代码与上面的DBR示例非常相似。
下面是Erik Dubelboer在PHP中使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | <? 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
我用它来对付彗星,我也用JavaGLAISFISH服务器建立了彗星,并通过订阅CeMeDayLycom发现了很多其他的例子。
看看这个博客文章,它有一个简单的聊天应用程序代码,在python/django/gevent中。
下面是我为Inform8Web开发的一个长轮询解决方案。基本上,重写类并实现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
赖安
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | 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 ou poller.htm中的一个小错误。
1 | 1000 /* ..after 1 seconds */ |
我想应该是
1 | "1000"); /* ..after 1 seconds */ |
为它工作。
对于那些感兴趣的人,我尝试了一个相当于姜戈的。启动一个新的Django项目,比如长轮询的lp:
1 | django-admin.py startproject lp |
为消息服务器调用应用程序msgsrv:
1 | python manage.py startapp msgsrv |
将以下行添加到settings.py以具有模板目录:
1 2 3 4 5 |
在urls.py中定义URL模式,如下所示:
1 2 3 4 5 6 7 | 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应该如下所示:
1 2 3 4 5 6 7 8 9 10 |
最后,templates/long_poller.htm应与上面相同,并更正拼写错误。希望这有帮助。
这是一个非常糟糕的PHP选择场景。如前所述,您可以非常快地将所有Apache工作人员绑定在一起,执行类似的操作。PHP是为开始、执行、停止而构建的。它不是为开始而构建的,等等…执行,停止。你会很快陷入服务器的困境,发现你有难以置信的伸缩问题。
也就是说,您仍然可以使用PHP来实现这一点,并且不必使用nginx httppushstreammodule:http://wiki.nginx.org/httppushstreammodule来终止服务器。
您在Apache(或其他)前面设置nginx,它将负责保持并发连接的开放性。您只需将数据发送到一个内部地址,就可以使用后台作业进行响应,或者只需将消息发送给等待新请求进入的人。这可以防止在长时间轮询期间PHP进程处于打开状态。
这不是PHP独有的,可以在任何后端语言中使用nginx。并发开放连接的负载等于node.js,所以最大的好处是它可以让您不需要这样的节点。
你会看到很多人提到其他语言库是为了完成长时间的投票,这是有充分理由的。PHP并不是为这种行为自然构建的。
为什么不考虑Web套接字而不是长轮询呢?它们效率高且易于安装。但是,它们仅在现代浏览器中受支持。这是一个快速参考。
WS-I小组发布了一个名为"可靠的安全概要"的东西,它有一个GlassFish和.NET实现,显然可以很好地进行交互操作。
幸运的是,这里也有一个JavaScript实现。
还有一个使用HTTP双工的Silverlight实现。您可以将javascript连接到Silverlight对象,以便在发生推送时获取回调。
也有商业付费版本。
对于ASP.NET MVC实现,请查看Nuget上可用的信号器。请注意,nuget通常是从git源代码中过期的,而git源代码的提交非常频繁。
在Scott Hanselman的博客上阅读更多关于信号器的信息
您可以尝试ICOMET(http://Github.com/IdWaWu/ICOMET),一个用LiBevor构建的C100K C++CeMET服务器。icomet还提供了一个javascript库,它使用起来非常简单
1 2 3 4 5 6 7 8 | 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)、IES(Windows)、Firefox、Chrome等。
最简单的节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | const http = require('http'); const server = http.createServer((req, res) => { SomeVeryLongAction(res); }); server.on('clientError', (err, socket) => { socket.end('HTTP/1.1 400 Bad Request '); }); server.listen(8000); // the long running task - simplified to setTimeout here // but can be async, wait from websocket service - whatever really function SomeVeryLongAction(response) { setTimeout(response.end, 10000); } |
在Express for ExMaple中,您可以在中间件中获得
如果你不知道我所说的范围界定是什么意思,这应该能让你知道
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | const http = require('http'); var responsesArray = []; const server = http.createServer((req, res) => { // not dealing with connection // put it on stack (array in this case) responsesArray.push(res); // end this is where normal api flow ends }); server.on('clientError', (err, socket) => { socket.end('HTTP/1.1 400 Bad Request '); }); // and eventually when we are ready to resolve // that if is there just to ensure you actually // called endpoint before the timeout kicks in function SomeVeryLongAction() { if ( responsesArray.length ) { let localResponse = responsesArray.shift(); localResponse.end(); } } // simulate some action out of endpoint flow setTimeout(SomeVeryLongAction, 10000); server.listen(8000); |
正如您所看到的,您可以真正地响应所有连接,一个,做任何您想要的。每个请求都有EDOCX1[2],因此您应该能够使用map和访问特定的out-api调用。