Answers:
它比我最初想象的要简单。基本上,您只有一个页面不执行任何操作,直到您要发送的数据可用(例如,收到新消息)为止。
这是一个非常基本的示例,它会在2-10秒后发送一个简单的字符串。1/3的机会返回错误404(以显示即将到来的Javascript示例中的错误处理)
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这样的常规Web服务器上运行该站点将很快占用所有“工作线程”,并使它无法响应其他请求。有很多解决方法,但是建议编写类似于Python的Twisted的“长轮询服务器” ,该服务器不依赖每个请求一个线程。cometD是一种流行的语言(有多种语言可用),而Tornado是专门为此类任务创建的新框架(它是为FriendFeed的长轮询代码构建的)...但是作为一个简单的示例,Apache绰绰有余!该脚本可以很容易地用任何一种语言编写(我选择了Apache / PHP,因为它们很常见,而我恰巧是在本地运行它们)
然后,在Javascript中,您请求上述文件(msg_srv.php
),然后等待响应。当您得到一个时,就对数据进行操作。然后,您请求文件并再次等待,对数据进行操作(并重复)
以下是此类页面的示例。.加载页面后,它将发送对msgsrv.php
文件的初始请求。如果成功,则将消息附加到#messages
div,然后在1秒钟后再次调用waitForMsg函数,触发等待。
1秒setTimeout()
是一个非常基本的速率限制器,没有此限制,它就可以正常工作,但是如果msgsrv.php
总是立即返回(例如,出现语法错误),则会使浏览器泛滥,并迅速冻结。最好检查文件是否包含有效的JSON响应,和/或保持每分钟/秒的运行请求总数,并适当地暂停。
如果页面错误,它将错误附加到#messages
div,等待15秒,然后重试(与我们在每条消息后等待1秒的方式相同)
这种方法的好处是它非常灵活。如果客户端的互联网连接断开,它将超时,然后尝试重新连接-这是轮询工作多长时间所固有的,不需要复杂的错误处理
无论如何,long_poller.htm
使用jQuery框架的代码:
<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>
sleep(rand(2,10));
?为了什么也不做,每100毫秒轮询一次数据库?什么时候决定死?
作为slosh的一部分,我有一个非常简单的聊天示例。
编辑:(因为每个人都在这里粘贴他们的代码)
这是使用long-polling和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>
我认为客户端看起来像一个普通的异步AJAX请求,但是您希望它花很长时间才能回来。
然后服务器看起来像这样。
while (!hasNewData())
usleep(50);
outputNewData();
因此,AJAX请求将发送到服务器,其中可能包括上次更新时间的时间戳,以便您hasNewData()
知道已获取的数据。然后,服务器处于循环睡眠状态,直到有新数据可用为止。一直以来,您的AJAX请求仍处于连接状态,只是挂在那里等待数据。最后,当有新数据可用时,服务器会将其提供给您的AJAX请求并关闭连接。
这是一些我在C#中用于长轮询的类。基本上有6个班级(见下文)。
这是一个不错的5分钟截屏视频,介绍了如何使用PHP和jQuery进行长时间轮询:http : //screenr.com/SNH
代码与上面dbr的示例非常相似。
这是Erik Dubbelboer在PHP中使用Content-type: multipart/x-mixed-replace
标头的简单长轮询示例:
<?
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开发的长轮询解决方案。基本上,您可以重写该类并实现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应该与上面的相同,并且输入错误得到纠正。希望这可以帮助。
"15000"
是语法错误。setTimeout将整数作为其第二个参数。
这是PHP是非常糟糕的选择的场景之一。如前所述,您可以快速捆绑所有Apache工作者,执行类似的操作。PHP是为启动,执行,停止而构建的。它不是为启动而创建的,请稍等...执行,停止。您将很快停顿服务器,发现您遇到难以置信的扩展问题。
也就是说,您仍然可以使用PHP进行此操作,并且不使用nginx HttpPushStreamModule杀死服务器:http ://wiki.nginx.org/HttpPushStreamModule
您可以在Apache(或其他任何工具)之前设置nginx,它将负责保持打开并发连接。您只需通过将数据发送到内部地址来响应有效负载,这可以通过后台作业来完成,或者只是在新请求到来时将消息发送给正在等待的人。这可以防止PHP进程在长时间轮询期间处于打开状态。
这不是PHP独有的,可以使用nginx和任何后端语言来完成。并发开放连接负载等于Node.js,因此最大的好处就是它可以使您摆脱NEEDING Node的负担。
您会看到很多其他人提到其他语言库来完成长时间轮询,这是有充分理由的。自然,PHP并不是针对这种行为而构建的。
WS-I组发布了一种称为“可靠的安全配置文件”的文件,该文件具有Glass Fish和.NET的实现,显然可以很好地实现互操作。
运气好的话,还有Java脚本实现。
还有一个使用HTTP Duplex的Silverlight实现。 您可以将JavaScript连接到Silverlight对象,以在发生推送时获取回调。
也有商业付费版本。
对于ASP.NET MVC实现,看看SignalR ,您可在的NuGet ..请注意,是的NuGet往往出之日起的Git的源这变得很频繁的提交。
在Scott Hanselman的博客上阅读有关SignalR的更多信息
您可以尝试icomet(https://github.com/ideawu/icomet),这是一个使用libevent构建的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等。
最简单的NodeJS
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\r\n\r\n');
});
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中的生产明智方案,例如您将response
在中间件中获得。您是否需要做的事情,可以将所有长期轮询的方法扩展到Map或其他对象(其他流程可以看到),并<Response> response.end()
在准备就绪时调用它。长时间轮询的连接没有什么特别的。其余就是您通常构建应用程序的方式。
如果您不了解搜寻范围是什么意思,这应该能让您了解
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\r\n\r\n');
});
// 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);
如您所见,您可以真正响应所有连接,一个,随心所欲。有id
每个请求,因此您应该能够使用map并通过api调用访问特定的请求。