PHP connection_aborted() работает неправильно

У меня есть следующий код:

ignore_user_abort(true);
while(!connection_aborted()) {
    // do stuff
}

и, согласно документации PHP, это должно работать до тех пор, пока соединение не будет закрыто, но по какой-то причине это не так, вместо этого оно продолжает работать до истечения срока действия сценария. Я посмотрел в Интернете и некоторые рекомендовали добавить

echo chr(0);
flush();

в петлю, но это, похоже, тоже ничего не делает. Еще хуже, если я просто оставлю это как

while(true) {
    // do stuff
}

PHP по-прежнему продолжает выполнять скрипт после отключения клиента. Кто-нибудь знает, как заставить это работать? Есть ли параметр php.ini, который мне где-то не хватает?

Если это имеет значение, я использую PHP 5.3.5. Заранее спасибо!


person kevmo314    schedule 17.06.2011    source источник


Ответы (4)


Пытаться:

    ignore_user_abort(true);

    echo "Testing connection handling";

    while (1) {
            if (connection_status() != CONNECTION_NORMAL)
                    break;
            sleep(1);
            echo "test";
            flush();
    }
person j.w.r    schedule 17.06.2011
comment
Нет, вроде тоже не работал. Однако стоит отметить, что когда я запускаю это в своем браузере, я не вижу, чтобы тест появлялся каждую секунду. Другими словами, это не похоже на то, что скрипт сбрасывается, что наводит меня на мысль, что это может быть настройка php.ini, которую мне не хватает? - person kevmo314; 17.06.2011
comment
Какой параметр output_buffering установлен в вашем php.ini? - person j.w.r; 17.06.2011
comment
output_buffering = 4096 Должен ли я попробовать отключить его? - person kevmo314; 17.06.2011
comment
Да, я думаю, вам, вероятно, потребуется перезапустить программное обеспечение веб-сервера после внесения этого изменения. - person j.w.r; 17.06.2011
comment
Похоже, это сработало, так что была настройка php.ini, которая меня подвела. Спасибо! - person kevmo314; 17.06.2011
comment
-1 Это не лучший ответ здесь. Как упомянул OP, причиной проблемы здесь был php.ini. Лучший ответ находится на stackoverflow.com/a/58775947/609862 в этой теме от @JS_Riddler. В нем объясняется основная проблема, из-за которой connection_aborted() не работает должным образом. - person asiby; 17.02.2020

Я немного опоздал на эту вечеринку, но у меня была эта проблема, и я докопался до ее сути. Здесь происходит несколько вещей — некоторые из них упомянуты здесь: 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 уже выключается, он выйдет из любой функции выключения, в которой он находится, и перейдет к следующей.

Деструкторы

Если вы хотите что-то делать, когда пользователь разрывает соединение, вам нужно сделать три вещи:

  1. Обнаружить, что пользователь прервал соединение. Это означает, что вы должны периодически пытаться 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

Попробуйте использовать ob_flush(); непосредственно перед flush();, и некоторые браузеры просто не будут обновлять страницу до добавления некоторых данных.

Попробуйте сделать что-то вроде

<? php
// preceding scripts

ignore_user_abort(true);

$i = 0;

while(!connection_aborted())
{ $i++;
  echo $i;

  echo str_pad('',4096); // yes i know this will increase the overhead but that can be reduced afterwords

  ob_flush();

  flush();

  usleep(30000); // see what happens when u run this on my WAMP this runs perfectly
}

// Ending scripts
?>

На самом деле у Google Chrome есть проблемы с этим кодом; он не очень хорошо поддерживает потоковую передачу.

person Community    schedule 22.06.2011

Буферизация, похоже, вызывает проблемы в зависимости от настроек вашего сервера.

Я попытался отключить буфер с помощью ob_end_clean, но этого было недостаточно, мне пришлось отправить некоторые данные, чтобы буфер полностью опустел. Вот окончательный код, который в итоге сработал для меня.

set_time_limit(0); // run the delay as long as the user stays connected
ignore_user_abort(false);
ob_end_clean();
echo "\n";
while ($delay-- > 0 && !connection_aborted())
{
    echo str_repeat("\r", 1000) . "<!--sleep-->\n";
    flush();
    sleep(1);
}
ob_start();
person Glenn J. Schworak    schedule 25.12.2020