Как стать автором
Поиск
Написать публикацию
Обновить

Показываем процесс работы непрерывной задачи на сервере, используя одно соединение

Время на прочтение3 мин
Количество просмотров8.8K
Мне было необходимо сделать показ интерактивного выполнения работы скрипта пользователю. Я реализовал многопоточного PHP-бота, выполняющего фоновую задачу получая запросы на выполнение. Результаты своей деятельности он записывает в базу. Дальше мне нужно было каким-то образом информировать пользователя о процессе выполнения.

Обычно в данном случае делают к примеру долгий запрос, по окончанию которого с сервера идет ответ о текущем состоянии, после чего идет снова повтор, либо проверять состояние каждые пару секунд. Но мне хотелось реализовать это в более живом виде, используя одно соединение.

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

Как будем действовать?


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

К сожалению, многие браузеры не умеют правильно обрабатывать состояние передачи данных в AJAX, поэтому сделаем условную синхронизацию данных в секундах. Будем через каждые 2 секунды сбрасывать данные с сервера, и каждые 2 секунды проверять приход новых данных на стороне клиента. В данном случае часто происходят нестыковки, какие то данные пришли уже более новые чем нужны, либо новых данных еще нет. Я буду учитывать лишь первый случай, т.к. предполагается что ваш пинг достаточно высок, чтобы обеспечить приход данных в избытке.

Заставляем сервер отдавать данные


Для этого нам надо запретить все методы сжатия, сообщить браузеру, что мы передаем поток, и заставить сбрасывать все выводимые данные пользователю.

Также одним из самых данных пунктов — необходимо закрыть запись открытой сессии. Дело в том, что PHP не может открыть одну и ту же сессию в двух параллельных потоках, поэтому в случае если ваш процесс будет работать, а пользовать сделает параллельный запрос — он будет висеть в очереди. В итоге все функции AJAX у вас будут парализированы.

header('Content-Encoding: none;'); //вырубаем сжатие
header('Content-type: application/octet-stream');

// выключаем буферизацию
ini_set('output_buffering', 'off');
// выключаем сжатие со стороны php
ini_set('zlib.output_compression', false);
// включаем сброс данных в браузер после каждого вывода
ini_set('implicit_flush', true);
ob_implicit_flush(true);
// закрываем сессию на запись, после этой команды нельзя будет записывать данные в сессию (!)
session_write_close();

for ($i = 0;$i < 20; $i++) {
    echo '|'.$i.str_repeat(' ', 4096).PHP_EOL;
    flush();
    sleep(2);
}

Выше я использую разделитель |, чтобы выводить только свежие данные и разделить переданные данные на логические части.

Заметьте — мы забиваем лишние 4096 байт, чтобы заставить отправить пакет пользователю. Иначе веб-сервер будет кэшировать данные, пока они не соберутся в необходимый для отправки пакет. Возможно, существует метод отправлять и без этого, буду готов выслушать решения в комментариях.

Скрипт выше будет каждые 2 секунды передавать числа от 0 до 19. Браузер будет выводить только наиболее свежие данные.

Клиентская часть


var xhr;
var timerlive;

function live(cont){
    try{
        xhr.abort();
    }catch(e) {
     }
    var temp;
    clearTimeout(timerlive);
    var prevtext = '';
    xhr = new XMLHttpRequest();
    xhr.open('POST', 'ajax.php', true);
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    xhr.send("type=live");
    
    timerlive = setInterval(function() {
        if (xhr.readyState == 4) {
            clearTimeout(timerlive);
        }
        temp=xhr.responseText.substring(prevtext.length);
        $('#'+cont).html(temp.substr(temp.lastIndexOf("|")+1));
        prevtext =  xhr.responseText;
    }, 2000);
}

function breaklive(){
    try{
        xhr.abort();
    }catch(e) {
        
     }
    clearTimeout(timerlive);
}


Вызов функции live() сначала прервет предыдущий «живой» канал, а потом инициализирует новое соединение с сервером. Мы будем записывать старые данные, чтобы знать точку отсчета для новых данных. Также используя разделитель | мы будем отделать только самые свежие данные из полученных. Вообще, можно просто отделять только самые свежие данные, однако вы можете обработать все новые данные, если у вас к примеру генерируется онлайн-лог.

Ниже предусмотрена функция для прерывания соедиенения, её можно встроить в ваши AJAX скрипты, чтобы при переходе на другую страницу у вас закрывалось соединение.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Как вы уведомляете пользователя о состоянии выполнения задачи?
7.41% Использую метод схожий с методом из статьи4
48.15% Проверяю через периоды времени состояние, совершая отдельные запросы26
33.33% Уведомляю пользователя лишь о завершении процесса используя Long-Pooling18
27.78% Вообще не уведомляю пользователя, надо будет, сам обновит страничку и увидит.15
Проголосовали 54 пользователя. Воздержались 49 пользователей.
Теги:
Хабы:
Всего голосов 6: ↑3 и ↓30
Комментарии3

Публикации

Ближайшие события