PHP connection_aborted() 无法正常工作

kev*_*314 7 php connection timeout infinite-loop

我有以下代码:

ignore_user_abort(true);
while(!connection_aborted()) {
    // do stuff
}
Run Code Online (Sandbox Code Playgroud)

根据 PHP 文档,这应该运行到连接关闭为止,但由于某种原因,它没有运行,而是一直运行到脚本超时。我在网上浏览了一下,有些建议添加

echo chr(0);
flush();
Run Code Online (Sandbox Code Playgroud)

进入循环,但这似乎也没有做任何事情。更糟糕的是,如果我把它保留为

while(true) {
    // do stuff
}
Run Code Online (Sandbox Code Playgroud)

客户端断开连接后,PHP 仍然继续运行脚本。有谁知道如何让它工作?我是否在某处缺少 php.ini 设置?

如果重要的话,我正在运行 PHP 5.3.5。提前致谢!

JS_*_*ler 10

我参加这个聚会有点晚了,但我刚刚遇到了这个问题并找到了答案。这里发生了很多事情——这里提到了其中一些: PHP根本不检测连接中止

要点: 为了connection_aborted()工作,PHP 需要尝试将数据发送到客户端。

输出缓冲器

如前所述,PHP 在尝试实际向客户端发送数据之前不会检测到连接已断开。这并不像执行 操作那么简单echo,因为echo将数据发送到任何output buffers可能存在的缓冲区,并且在这些缓冲区足够满之前,PHP 不会尝试真正的发送。我不会详细介绍输出缓冲,但值得一提的是,可以有多个嵌套缓冲区。

无论如何,如果您想测试connection_abort(),您必须首先结束所有缓冲区:

while (ob_get_level()){ ob_end_clean(); }
Run Code Online (Sandbox Code Playgroud)

现在,只要您想测试连接是否中止,您就必须尝试向客户端发送数据:

echo "Something.";
flush();

// returns expected value...
// ... but only if ignore_user_abort is false!
connection_aborted(); 
Run Code Online (Sandbox Code Playgroud)

忽略用户中止

这是一个非常重要的设置,它决定了当调用上述内容flush()并且用户已中止连接(例如:点击浏览器中的“停止”按钮)时 PHP 将执行的操作。

如果true,脚本将愉快地运行。flush()基本上什么也不做。

如果为false(默认设置),执行将按以下方式立即停止:

  • 如果 PHP 尚未关闭,它将开始其关闭过程。

  • 如果 PHP 已经关闭,它将退出它所在的任何关闭函数并继续执行下一个。

析构函数

如果您想在用户中止连接时执行某些操作,则需要执行三件事:

  1. 检测用户中止连接。这意味着您必须flush定期尝试与用户联系,如上所述。清除所有输出缓冲区、回显、刷新。

    A。如果ignore_connection_aborted是 true,则需要connection_aborted()在每次刷新后手动测试。

    b. 如果ignore_connection_aborted为 false,则调用flush将导致关闭过程开始。然后,您必须特别小心,不要从关闭函数内部引起flush,否则 PHP 将立即停止执行该函数并继续执行下一个关闭函数。

把它们放在一起

将所有这些放在一起,让我们做一个检测用户点击“停止”并执行操作的示例。

class DestructTester {
    private $fileHandle;

    public function __construct($fileHandle){
        // fileHandle that we log to
        $this->fileHandle = $fileHandle;
        // call $this->onShutdown() when PHP is shutting down.
        register_shutdown_function(array($this, "onShutdown"));
    }

    public function onShutdown() {
        $isAborted = connection_aborted();
        fwrite($this->fileHandle, "PHP is shutting down. isAborted: $isAborted\n");

        // NOTE
        // If connection_aborted() AND ignore_user_abort = false, PHP will immediately terminate
        // this function when it encounters flush. This means your shutdown functions can end
        // prematurely if: connection is aborted, ignore_user_abort=false, and you try to flush().
        echo "Test.";
        flush();
        fwrite($this->fileHandle, "This was written after a flush.\n");
    }
    public function __destruct() {
        $isAborted = connection_aborted();
        fwrite($this->fileHandle, "DestructTester is getting destructed. isAborted: $isAborted\n");
    }
}

// Create a DestructTester
// It'll log to our file on PHP shutdown and __destruct().
$fileHandle = fopen("/path/to/destruct-tester-log.txt", "a+");
fwrite($fileHandle, "---BEGINNING TEST---\n");
$dt = new DestructTester($fileHandle);

// Set this value to see how the logs end up changing
// ignore_user_abort(true);

// Remove any buffers so that PHP attempts to send data on flush();
while (ob_get_level()){
    ob_get_contents();
    ob_end_clean();
}

// Let's loop for 10 seconds
//   If ignore_user_abort=true:
//      This will continue to run regardless.
//   If ignore_user_abort=false:
//      This will immediate terminate when the user disconnects and PHP tries to flush();
//      PHP will begin its shutdown process.
// In either case, connection_aborted() should subsequently return "true" after the user
// has disconnected (hit STOP button in browser), AND after PHP has attempted to flush().
$numSleeps = 0;
while ($numSleeps++ < 10) {
    $connAbortedStr = connection_aborted() ? "YES" : "NO";
    $str = "Slept $numSleeps times. Connection aborted: $connAbortedStr";
    echo "$str<br>";
    // If ignore_user_abort = false, script will terminate right here.
    // Shutdown functions will being.
    // Otherwise, script will continue for all 10 loops and then shutdown.
    flush();

    $connAbortedStr = connection_aborted() ? "YES" : "NO";
    fwrite($fileHandle, "flush()'d $numSleeps times. Connection aborted is now: $connAbortedStr\n");
    sleep(1);
}
echo "DONE SLEEPING!<br>";
die;
Run Code Online (Sandbox Code Playgroud)

评论说明了一切。您可以摆弄ignore_user_abort并查看日志,看看这会如何改变事情。

我希望这可以帮助任何遇到connection_abortregister_shutdown_function和问题的人__destruct