Я немного опоздал на эту вечеринку, но у меня была эта проблема, и я докопался до ее сути. Здесь происходит несколько вещей — некоторые из них упомянуты здесь: PHP не вообще не обнаруживает разрыв соединения
Суть этого: Чтобы 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();
Игнорировать отмену пользователя
Это очень важный параметр, который определяет, что будет делать PHP, когда вызывается вышеуказанный flush()
, а пользователь прерывает соединение (например, нажимает кнопку STOP в своем браузере).
Если true, скрипт будет работать весело. flush()
практически ничего не сделает.
Если false, как это установлено по умолчанию, выполнение будет немедленно остановлено следующим образом:
Если PHP еще не выключается, он начнет процесс завершения работы.
Если PHP уже выключается, он выйдет из любой функции выключения, в которой он находится, и перейдет к следующей.
Деструкторы
Если вы хотите что-то делать, когда пользователь разрывает соединение, вам нужно сделать три вещи:
Обнаружить, что пользователь прервал соединение. Это означает, что вы должны периодически пытаться flush
обращаться к пользователю, как описано выше. Очистить все буферы вывода, эхо, сброс.
а. Если ignore_connection_aborted
истинно, вам нужно вручную проверять connection_aborted()
после каждого сброса.
б. Если 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;
Комментарии все объясняют. Вы можете поиграться с ignore_user_abort
и посмотреть журналы, чтобы увидеть, как это меняет ситуацию.
Я надеюсь, что это поможет всем, у кого есть проблемы с connection_abort
, register_shutdown_function
и __destruct
.
person
JS_Riddler
schedule
09.11.2019