【问题标题】:PHP Event Source keeps executingPHP 事件源不断执行
【发布时间】:2012-10-06 18:52:03
【问题描述】:

我开始使用 JavaScript EventSource 对象在 HTML5 中使用 push。我对 PHP 中的工作解决方案非常满意:

$time = 0;
while(true) {
    if(connection_status() != CONNECTION_NORMAL) {
        mysql_close()
        break;
    }
    $result = mysql_query("SELECT `id` FROM `table` WHERE UNIX_TIMESTAMP(`lastUpdate`) > '".$time."'");
    while($row = mysql_fetch_array($result)) {
        echo "data:".$row["id"].PHP_EOL;
        echo PHP_EOL;
        ob_flush();
        flush();
    }
    $time = time();
    sleep(1);
}

但突然间,我的 WebApp 因 MySQL 错误“连接过多”而无法访问。

原来在JavaScript中关闭事件源后MySQL连接没有关闭:

window.onload = function() {
    sse = new EventSource("push.php");
    sse.onmessage = function(event) {
        console.log(event.data.split(":")[1]);
    }
}
window.onbeforeunload = function() {
    sse.close();
}

所以我猜PHP脚本不会停止执行。在客户端连接断开之前,有什么方法可以调用函数(如die();)?为什么我的脚本在 EventSource 上调用 .close(); 后没有终止?!

感谢您的帮助! ——

【问题讨论】:

  • 似乎每个客户端会话都使用自己的mysql连接启动一个新的服务器端无限循环,不是吗?我看不到您发布的示例中的连接开始,所以我不确定。无论如何,这就是出现“连接太多”错误的原因。提供数据的服务器端服务可以使用(共享)单个数据库连接(也可以使用单个无限循环)。我没有将此作为答案发布,因为我手头没有示例。自从几年以来一直没有使用PHP。虽然我在 php.net 上看到 mysql_query 有点过时了。他们建议改用 MySQLi 或 PDO_MySQL。
  • 那种。每个客户端会话都有自己的 mysql 连接。连接在无限循环之前开始。 真正的问题是,调用EventSource.close() 不会关闭或中止连接本身!
  • 据我了解,通过 push.php 提供的内容对于每个客户端都是相同的,不是吗?为什么然后连接到数据库并在每个客户端会话中进行查询,而不是在所有客户端会话之间共享相同的 SQL 结果?尽管它是更优化的解决方案,但它也可以解决您的问题。
  • 听起来我应该尝试一下...当另一个客户端更改数据库时,我需要我的脚本将信息推送到客户端。如何在不为每个客户端建立数据库连接的情况下将结果共享给所有客户端?!我需要立即推送信息。
  • 如果没有进一步的研究,我不知道确切的答案。我几年前使用过 PHP。但是尝试搜索诸如“应用程序范围”、“有状态服务”和“cron 作业”之类的内容,并添加关键字 PHP。也许你可以把一些东西粘在一起。

标签: php javascript html server-sent-events


【解决方案1】:

首先:当您将代码包装在一个巨大的while(true) 循环中时,您的脚本将永远不会终止。当您的脚本“没有要执行的代码” 时,与数据库的连接将被关闭,但是由于您已经编写了死锁,所以这不会发生......永远。
这不是EventSource 问题,它只是做了它应该做的事情。老实说,真的,可悲的是你的错。

问题是:用户连接到您的站点,EventSource 对象被实例化。建立到服务器的连接并请求返回值push.php。您的服务器按要求执行并运行脚本,这 - 再次 - 只不过是死锁。没有错误,所以只要 php.ini 允许它运行,它就可以继续运行。 .close() 方法确实取消了输出流(或者应该取消),但是您的脚本正忙于执行其无限循环或休眠。假设由于客户端断开连接而停止脚本就像假设 any 客户端可能会干扰服务器所做的事情。安全方面,这将是可能发生的最糟糕的情况。现在,回答您的实际问题:是什么导致了问题,如何解决?
答案:HEADERS

查看this little PHP example:脚本不是在服务器端保持活动状态(通过无限循环),而是通过设置正确的标头。

顺便说一句:请尽快放弃mysql_*,它已被弃用(最终),改用PDOmysqli_*。老实说,这并不难。

【讨论】:

  • 跟随你的论点:调用 close();不会改变connection_status(); 返回的连接状态?从什么时候开始可以从 JavaScript 编辑标题?!当我从 PHP 终止脚本时,新数据将永远不会推送到客户端。当将标头设置为“文本/事件流”时(就像我一样)永远不会告诉 mysql 检查更新,对吧?
  • @Julian: 调用 .close() 确实会改变 connecion_status。您可以在 JS 中设置 请求标头(只需查看 ajax 调用:setRequestHeader),但我指的是 PHP 的 header('some header');。您也不必显式终止 PHP 脚本。只需查看我提供的链接,它包含更多详细信息,并提供了有关如何使其正常工作的非常清晰的示例
  • 我知道链接。参考哪一段?我需要 PHP 脚本来持续检查我的数据库。在我的 PHP 中,我每秒将连接状态写入一个文本文件。当我调用.colse() 时,状态不会改变。很遗憾。我无法更改 PHP 中的标头,因为脚本必须连续运行,直到客户端断开连接! while(connection_staus() == CONNECTION_NORMAL) {} 也不起作用...
  • 嗯...那行不通。自从我开始进行推送以来,我一直在使用“文本/事件流”。当我删除 while 循环时,事件源对象返回 on error 函数并关闭连接。当我没有循环时,脚本完成执行并终止。然后关闭流。标题接缝不会打扰它。调用 .close() 仍然不会更改 connection_status。无法想象为什么......
  • @EliasVanOotegem,您在链接中引用的示例未通过 Headers 保持活动状态。事实上,它实际上死了,客户端在三秒后重新打开连接。
【解决方案2】:

我遇到了完全相同的问题,据我所知,原因是 apache 没有终止 php 脚本,直到我再次尝试通过关闭的套接字连接写入数据。

我的数据库中没有任何更改,因此没有输出。

为了解决这个问题,我只需在我的 while 循环中回显一个事件源注释,例如:

echo ": heartbeat\n\n";
ob_flush();
flush();

如果套接字连接关闭,这会导致脚本终止。

【讨论】:

  • 是的,这行得通。但相比之下,这会产生“巨大的”网络开销。这并不比不断轮询好多少……
  • 取决于您发送它的频率。您不需要在每个循环上都这样做。您可以每 15 分钟左右执行一次...重要的是脚本会终止,如果它立即终止,则不会真正终止。
  • 同意,你完全正确。但我总是试图让事情变得完美;)所以你明白为什么这不“尝起来”那么好 :D 但是,是的,谢谢你的建议,这绝对使它可行,即使不完美。我只是认为 PHP 不适合让它变得完美。
  • 我也喜欢把事情做得完美我明白你的意思。能够像我的应用程序的整个其余部分一样使用 php 来做到这一点使它变得容易得多。如果我必须用其他东西来做事件源部分,我可能会切换到 WebSockets。
【解决方案3】:

您可能想尝试将其添加到循环中:

if(connection_status() != CONNECTION_NORMAL)
{
    break;
}

但是当客户端断开连接时php应该停止。

【讨论】:

  • 我假设 PHP 也应该停止。我使用 if 语句尝试了您的解决方案,然后是 mysql_close();,然后是 break;。它没有帮助。当我转到我的 MySQL 客户端并输入 SHOW PROCESSLIST 时,我的 WebApps 用户仍处于连接状态。每次我重新加载页面时,它都会连接一次……也许是 JS EventSource.clos(); 问题???
  • if(connection_aborted()) 会更好吗?
【解决方案4】:

1.

window.onbeforeunload = function() {
    sse.close();
}

这不是必需的,EventSource 将在页面卸载时关闭。

  1. 即使在 PHP 上找不到检测断开客户端的方法,也可以在 N 秒后停止执行,EventSource 会自动重新连接。

http://www.php.net/manual/ru/function.connection-aborted.php 此页面上的 cmets 说,您应该在 connection_status 之前使用“flush” 无论如何,您应该在 N 秒后断开连接,因为您无法检测到“坏”断开连接。

3。 回声“重试:1000\n”; // 用它来告诉 EventSource 重新连接延迟(毫秒)

【讨论】:

  • 谢谢,这是一种解决方法。但这似乎也不是正确的方法,因为当 PHP 脚本停止执行时 EventStream 会出错。
  • 和?可以得到这个错误,你应该检查错误处理程序中的 readyState,如果它是 EventSource.CONNECTED - 那么 ES 将尝试重新连接
【解决方案5】:
$time = 0;
while(true) { 
    if(connection_status() != CONNECTION_NORMAL) {
        mysql_close()
        break;
    }
    $result = mysql_query("SELECT id FROM table WHERE UNIX_TIMESTAMP(lastUpdate) > '".$time."'");
    while($row = mysql_fetch_array($result)) {
        $rect[] = $row;
    }
    for($i-0;$i echo "data:".implode('',$disp)."\n\n"; $time = time(); sleep(1);
}

【讨论】:

    【解决方案6】:

    正如 Elias Van Ootegem 所指出的,您的脚本永远不会终止,因此您有一堆处于活动状态的 MySQL 连接。更令人担忧的是,您有大量资源以 PHP 循环的形式使用,而且看不到尽头!

    不幸的是,保留的解决方案不是“标题”。在此链接referenced by Elias 的示例中,php 脚本终止,客户端 javascript 实际上必须重新打开连接。您可以使用以下代码对此进行测试

    source.addEventListener('open', function(e) {
      // Connection was opened.
        console.log("Opening new connection");
    }, false);
    

    如果您按照他在 github 上的示例,在实现中作者实际上使用了一个在 X 秒后终止的循环。 See the do loop and comments at bottom of the script.

    解决方案是给你的循环一个生命周期。

    通过定义循环、IE X 迭代或 X 时间后触发循环结束或die(); 的限制,您将使其在客户端断开连接时限制脚本将保持活动状态。如果客户端在 X 时间后仍然存在,他们将简单地重新连接。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多