我参加这个聚会有点晚了,但我刚刚遇到了这个问题,并把它弄明白了。这里发生了很多事情——这里提到了其中的一些:
PHP doesn't detect connection abort at all
要点:为了让connection_aborted() 工作,PHP 需要尝试向客户端发送数据。
输出缓冲区
如前所述,PHP 在尝试实际向客户端发送数据之前不会检测到连接已断开。这不像echo 那样简单,因为echo 将数据发送到任何可能存在的output buffers,并且在这些缓冲区足够满之前,PHP 不会尝试真正的发送。我不会详细介绍输出缓冲,但值得一提的是,可以有多个嵌套的缓冲区。
无论如何,如果你想测试 connection_abort(),你必须首先结束所有的缓冲区:
while (ob_get_level()){ ob_end_clean(); }
现在,只要您想测试连接是否中止,您就必须尝试向客户端发送数据:
echo "Something.";
flush();
// returns expected value...
// ... but only if ignore_user_abort is false!
connection_aborted();
忽略用户中止
这是一个非常重要的设置,它决定了当上面的flush() 被调用并且用户中止连接(例如:点击浏览器中的停止按钮)时 PHP 将做什么。
如果true,脚本将愉快地运行。 flush() 基本上什么都不做。
如果false,按照默认设置,执行将立即以下列方式停止:
析构函数
如果你想在用户中止连接时做一些事情,你需要做三件事:
-
检测用户中止连接。这意味着您必须定期尝试向用户发送flush,如上文所述。清除所有输出缓冲区,回显,刷新。
一个。如果ignore_connection_aborted 为真,则需要在每次刷新后手动测试connection_aborted()。
b.如果ignore_connection_aborted 为假,则调用flush 将导致关闭过程开始。 您必须特别小心,不要在关闭函数中引起flush,否则 PHP 将立即停止执行该函数并继续执行下一个关闭函数。
把它们放在一起
综上所述,让我们举个例子,检测用户点击“STOP”并执行操作。
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;
cmets 解释了一切。您可以摆弄ignore_user_abort 并查看日志以了解这会如何改变事情。
我希望这可以帮助任何遇到connection_abort、register_shutdown_function 和__destruct 问题的人。