它比我最初想象的要简单.. 基本上你有一个页面什么都不做,直到你想要发送的数据可用(比如,一条新消息到达)。
这是一个非常基本的示例,它在 2-10 秒后发送一个简单的字符串。返回错误 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
always 立即返回(例如,出现语法错误) - 你会淹没浏览器并它可以迅速冻结。最好检查文件是否包含有效的 JSON 响应,和/或保持每分钟/秒的请求总数,并适当地暂停。
如果页面出错,它将错误附加到 #messages
div,等待 15 秒,然后重试(与我们在每条消息后等待 1 秒的方式相同)
这种方法的好处是它非常有弹性。如果客户端 Internet 连接中断,它将超时,然后尝试重新连接 - 这是轮询工作时间长短所固有的,不需要复杂的错误处理
无论如何,使用 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 的一部分,我有一个非常简单的聊天示例。
编辑:(因为每个人都在这里粘贴他们的代码)
这是使用长轮询和 slosh 的完整的基于 JSON 的多用户聊天。这是一个如何调用的demo,请忽略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>
getNewComments
回调,所以它只是在每个 ajax 请求结束时无休止地触发它
我认为客户端看起来像一个普通的异步 AJAX 请求,但您希望它需要“很长时间”才能回来。
然后服务器看起来像这样。
while (!hasNewData())
usleep(50);
outputNewData();
因此,AJAX 请求会发送到服务器,其中可能包含上次更新时间的时间戳,以便您的 hasNewData()
知道您已经获得了哪些数据。然后服务器处于循环休眠状态,直到有新数据可用。一直以来,您的 AJAX 请求仍处于连接状态,只是挂在那里等待数据。最后,当有新数据可用时,服务器会将其提供给您的 AJAX 请求并关闭连接。
Here 是我在 C# 中用于长轮询的一些类。基本上有6个类(见下文)。
控制器:处理创建有效响应所需的操作(数据库操作等) 处理器:管理与网页(本身)的异步通信 IAsynchProcessor:实现此接口的服务进程实例 服务:处理实现 IAsynchProcessor 的请求对象 请求:IAsynchProcessor包含您的响应(对象)的包装器响应:包含自定义对象或字段
这是一个不错的 5 分钟截屏视频,介绍如何使用 PHP & 进行长轮询。 jQuery:http://screenr.com/SNH
代码与上面 dbr 的示例非常相似。
这是使用 Content-type: multipart/x-mixed-replace
标头的 a simple long-polling example in PHP by Erik Dubbelboer:
<?
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 开发的一个长轮询解决方案。基本上,您重写该类并实现 loadData 方法。当 loadData 返回值或操作超时时,它将打印结果并返回。
如果您的脚本处理时间可能超过 30 秒,您可能需要将 set_time_limit() 调用更改为更长的时间。
阿帕奇 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();
}
这是 PHP 非常糟糕的选择之一。如前所述,您可以非常快速地捆绑所有 Apache 工作人员来执行类似的操作。 PHP 是为启动、执行、停止而构建的。它不是为启动、等待...执行、停止而构建的。你会很快让你的服务器陷入瘫痪,并发现你有令人难以置信的扩展问题。
也就是说,您仍然可以使用 PHP 执行此操作,并且不会使用 nginx HttpPushStreamModule 杀死您的服务器:http://wiki.nginx.org/HttpPushStreamModule
您在 Apache(或其他任何东西)前面设置 nginx,它将负责保持打开的并发连接。您只需通过向内部地址发送数据来响应有效负载,您可以使用后台作业执行此操作,或者只是将消息发送给在新请求进入时等待的人。这可以防止 PHP 进程在长时间轮询期间保持打开状态。
这不是 PHP 独有的,可以使用带有任何后端语言的 nginx 来完成。并发打开的连接负载等于 Node.js,所以最大的好处是它让你摆脱了类似这样的需要节点。
您会看到很多其他人提到其他语言库来完成长轮询,这是有充分理由的。 PHP 只是不适合这种类型的行为。
感谢您的代码,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 将整数作为其第二个参数。
为什么不考虑 Web 套接字而不是长轮询?它们非常高效且易于设置。但是,它们仅在现代浏览器中受支持。这是一个quick reference。
WS-I 小组发布了一个名为 "Reliable Secure Profile" 的东西,它有一条 Glass Fish 和 .NET implementation,显然 inter-operate 很好。
运气好的话,还有一个 Javascript 实现。
还有一个 Silverlight 实现,它使用 HTTP Duplex. 您可以 connect javascript to the Silverlight 对象在推送发生时获取回调。
对于 ASP.NET MVC 实现,请查看 SignalR which is available on NuGet。请注意,NuGet 通常与 Git source 相比已过时,Git source 提交非常频繁。
在 blog on by Scott Hanselman 上阅读有关 SignalR 的更多信息
您可以尝试使用 libevent 构建的 C1000K C++ comet 服务器 icomet(https://github.com/ideawu/icomet)。 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 调用。
不定期副业成功案例分享
sleep(rand(2,10));
?为了什么都不做,每 100 毫秒轮询一次数据库?它什么时候决定死?