这是我的问题:我有一个脚本(我们称其为comet.php),它被一个AJAX客户端脚本请求,并等待像这样发生的更改:
while(no_changes){
usleep(100000);
//check for changes
}
我不太喜欢这个,它不太可扩展,并且(在我看来)是“坏实践”。我想用信号量或任何并发编程技术来改进这种行为。你能给我一些如何处理它的提示吗?(我知道,这不是一个简短的答案,但一个起点就足够了。)
编辑:LibEvent 怎么样?
这是我的问题:我有一个脚本(我们称其为comet.php),它被一个AJAX客户端脚本请求,并等待像这样发生的更改:
while(no_changes){
usleep(100000);
//check for changes
}
var stream = new EventSource('stream.php');
stream.addEventListener('debug', function (event) {
var data = JSON.parse(event.data);
console.log([event.type, data]);
});
stream.addEventListener('message', function (event) {
var data = JSON.parse(event.data);
console.log([event.type, data]);
});
路由器.php
这是一个长期运行的进程,它监听传入的消息并将它们发送给任何正在监听的人。
<?php
$context = new ZMQContext();
$pull = $context->getSocket(ZMQ::SOCKET_PULL);
$pull->bind("tcp://*:5555");
$pub = $context->getSocket(ZMQ::SOCKET_PUB);
$pub->bind("tcp://*:5556");
while (true) {
$msg = $pull->recv();
echo "publishing received message $msg\n";
$pub->send($msg);
}
stream.php
每个连接到网站的用户都会获得自己的stream.php。这个脚本是长时间运行的并且等待来自路由器的任何消息。一旦它收到新消息,它就会以EventSource格式输出此消息。
<?php
$context = new ZMQContext();
$sock = $context->getSocket(ZMQ::SOCKET_SUB);
$sock->setSockOpt(ZMQ::SOCKOPT_SUBSCRIBE, "");
$sock->connect("tcp://127.0.0.1:5556");
set_time_limit(0);
ini_set('memory_limit', '512M');
header("Content-Type: text/event-stream");
header("Cache-Control: no-cache");
while (true) {
$msg = $sock->recv();
$event = json_decode($msg, true);
if (isset($event['type'])) {
echo "event: {$event['type']}\n";
}
$data = json_encode($event['data']);
echo "data: $data\n\n";
ob_flush();
flush();
}
要向所有用户发送消息,只需将其发送到路由器即可。然后,路由器将把该消息分发给所有正在侦听的流。以下是一个示例:
<?php
$context = new ZMQContext();
$sock = $context->getSocket(ZMQ::SOCKET_PUSH);
$sock->connect("tcp://127.0.0.1:5555");
$msg = json_encode(array('type' => 'debug', 'data' => array('foo', 'bar', 'baz')));
$sock->send($msg);
$msg = json_encode(array('data' => array('foo', 'bar', 'baz')));
$sock->send($msg);
这可以证明实时编程并不需要使用 node.js,PHP 也完全可以胜任。
除此之外,socket.io 是一个非常好的实现方式。而且你可以通过 ZeroMQ 轻松地将它连接到你的 PHP 代码中。
另请参阅
这取决于你在服务器端脚本中做什么。有些情况下,除了上述方法,你别无选择。
然而,如果你要调用一个函数等待某个事件发生,你可以使用这个技巧来避免竞争,而不是使用 usleep()
(这被认为是一种“不好的做法”)。
比如说,你正在等待来自文件或其他阻塞流的数据,你可以这样做:
while (($str = fgets($fp)) === FALSE) continue;
// Handle the event here
实际上,PHP并不是执行此类操作的最佳语言。但有些情况下(我知道,因为我自己处理过这种情况),PHP是唯一的选择。
fgets()
调用进行等待,而不是检查-睡眠-检查-睡眠-检查...在你的示例中,如果事件发生在 usleep()
期间,则必须等待睡眠结束才能处理它。通过将(阻塞)检查作为循环条件,一旦发生事件,fgets()
就会立即返回,然后您就可以立即处理它。 - DaveRandom你需要一个实时库。
其中一个例子是 Ratchet http://socketo.me/
负责发布订阅的部分在 http://socketo.me/docs/wamp 中有讨论。
这里的限制是 PHP 也需要是初始化可变数据的那个。
换句话说,这不会神奇地让你订阅 MySQL 更新。但是如果你可以编辑 MySQL 设置代码,那么就可以在那里添加发布部分。
我同意这里的共识,即PHP不是解决此问题的最佳方案。您确实需要寻找专用的实时技术来解决从服务器向客户端传递数据的异步问题。听起来您正在尝试实现HTTP-长轮询,这并不容易在跨浏览器上解决。Comet产品的开发人员已经多次解决了这个问题,因此我建议您考虑使用Comet解决方案,甚至更好的是WebSocket解决方案,并为旧版浏览器提供回退支持。
我建议您让PHP执行其擅长的Web应用程序功能,并选择专用解决方案来处理实时、事件驱动和异步功能。