Получение обратной связи от серверного процесса в режиме реального времени [на PHP]

Требование:

Мне нужно запустить фоновый процесс (по запросу пользователя), который занимает от 30 до 60 секунд. Я хотел бы дать пользователю обратную связь о состоянии. Примечание. Толи прав, "Фон" не требуется.

Что работает:

За это время процесс выводит около 20 сообщений о состоянии, и я извлекаю их с помощью proc_open и прослушиваю канал чтения с помощью fgets. Я могу сохранить эти сообщения в переменную сеанса и, используя временные метки (для помощи в отладке), я вижу, что массив сеансов записывается с этими сообщениями по мере выполнения процесса.

Проблема:

Мой план состоял в том, чтобы опросить сервер вызовами ajax (каждую секунду), чтобы получить эти переменные сеанса для отображения в DOM. Узким местом, по-видимому, является то, что сервер не может обслуживать запрос ajax, пока он все еще работает в фоновом режиме. Все сбрасывается сразу после завершения фонового процесса. Из того, что я могу сказать, проблема не в буферизации вывода, потому что использование (отладка) временных меток, сохраненных с каждым сообщением процесса, показывает, что сервер последовательно записывает в переменную сеанса, поэтому я знаю, что чтение proc_open и pipe работает, как я ожидаю . Похоже, что проблема заключается в том, что сервер не может предоставить AJAX-запросу свой объект JSON, пока он не будет выполнен с процессом; или, возможно, точнее, с циклом, который читает канал.

Очевидное заблуждение:

Я думал, что отправка процесса в фоновый режим (используя &) может дать мне решение здесь. Видимо я не знаю разницы между фоновым процессом и разветвленным процессом. Какая польза от запуска процесса в фоновом режиме, если это не имеет для меня никакого значения в этом сценарии?

Возможные решения:

  1. Я не ожидаю, что инициированный пользователем процесс, который запускает этот процесс/сценарий, будет таким тяжелым, но если есть что-то, что я могу встроить в это решение, что помогло бы при большой нагрузке, то я хотел бы сделать это сейчас.
  2. Является ли это многопоточным (pthreads) или многопроцессорным (fork) решением?
  3. Или мне следует сохранить идентификатор процесса, отпустить его опрос с помощью инструкции while( .. fgets ..), а затем вернуться к процессу после того, как сервер обслужит запрос ajax?
  4. Я полагаю, что мог бы запустить поддельные сообщения о состоянии, а затем точно ответить, когда результаты вернутся после завершения. Время обработки запроса не зависит от пользователя, поэтому мой поддельный тайминг может быть довольно точным. Тем не менее, я хотел бы знать, каким будет решение для обеспечения обратной связи в режиме реального времени.

person Ricalsin    schedule 13.04.2014    source источник


Ответы (2)


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

Немного важной теории: — session_start() и набор типа $_SESSION["x"] = "y" всегда будут блокировать файл сессии.

Сценарий: – A – process.php – выполнение вызова ajax – B – get_session.php – второй вызов ajax;

Основная проблема заключается в том, что даже если вы установите $_SESSION внутри процесса, который запускается через AJAX, ему всегда придется ждать разблокировки файла сеанса, и это приведет к синхронизации между двумя процессами. (А. + Б.) - оба заканчиваются одновременно!

Таким образом, самый простой способ решить эту проблему и получить хороший результат — использовать session_write_close() после каждого набора. Например.:

%_SESSION["A"] = "B";
$_SESSION["x"] = "y";
session_write_close();

PS: Лучший подход — иметь настраиваемый набор функций для обработки сеансов.

Извините за наценку. Я только что создал учетную запись стека.

person adelin    schedule 08.06.2014

Почему вы думаете, что вам нужен фоновый процесс? Кроме того, с чего вы взяли, что он вам нужен?

Обычный php-скрипт с установленным достаточным временем ожидания с функцией flush(), используемой на каждом этапе, даст вам результат, необходимый для вашего AJAX.

Что еще проще, так как вы используете сеансы - AJAX-запрос к отдельному обработчику, который просто проверит, что в сеансе, и если есть что-то новое - вернет вам новую часть.

$_SESSION['progress'] = array();

внутри процесса.php

$_SESSION['progress'][] = 'Done 5%';
// complete some commands
$_SESSION['progress'][] = 'Done 10%';

внутри ajax.php

if(count($_SESSION['progress']) > $_GET['laststep']) {
    // echo the new messages
}

внутри вашей обычной страницы

$.ajax('ajax.php', 'GET', 'laststep=1', success: function(data){ show(data);})

Что-то вроде этого должно работать.

person Anatoliy Kim    schedule 13.04.2014
comment
вы точно описываете мой подход, но у меня все же есть перечисленные проблемы. Если у меня есть цикл while, который удерживает меня на канале proc_open, даже если у него есть sleep(), похоже, он не обслуживает запрос ajax, пока он не завершится. Я все время сбрасывал буферы (все). Вы думаете, что вызов ajax обрабатывается «отдельным обработчиком», что означает отдельный процесс, и поэтому мои мысли о создании отдельного потока или процесса не нужны? - person Ricalsin; 14.04.2014
comment
«отдельный обработчик» означает отдельный php-скрипт. если основной процесс выполняется в process.php, создайте второй скрипт в отдельном файле, который будет обрабатывать запросы ajax. - person Anatoliy Kim; 14.04.2014
comment
Наличие отдельного обработчика и отдельного процесса — или другого потока внутри процесса — это разные вещи. If the main process is done... как бы подытоживает проблему. В php sleep() блокируется, если не будут предприняты шаги для решения процесса/потока, поэтому ваше предложение не (реально) осуществимо - оно выполнимо, но с более подробным объяснением (и, на мой взгляд, не стоит). Я ценю ваш ответ, но это не ответ на мой вопрос. - person Ricalsin; 14.04.2014
comment
Как я пытаюсь вам объяснить - вам не нужна функция sleep(). Предположим, вы выполняете некоторые операции в php-скрипте под названием «process.php». Выполняя эти трудоемкие операции, вы можете периодически обновлять переменную $_SESSION. Запросы от вашего клиента должны быть отправлены в другой php-скрипт, назовем его «ajax_handler.php». В ajax_handler.php вы можете получить доступ к текущему значению переменной $_SESSION и вернуть прогресс в браузер. - person Anatoliy Kim; 15.04.2014
comment
если вы не создадите версию PHP, которая может либо разветвлять процессы, либо запускать несколько потоков, то то, что вы предлагаете, невозможно; потому что просто отправка запроса другому сценарию не означает, что он будет (немедленно) запущен, если интерпретатор занят выполнением чего-то еще - отсюда и понимание того, что PHP является однопоточным ИЗ КОРОБКИ. Пожалуйста, погуглите pThreads и Zend Thread Safety (ZTS), чтобы лучше понять, как эти вещи работают вместе, чтобы фактически обеспечить многопоточность в среде PHP. - person Ricalsin; 15.04.2014
comment
Разветвленные процессы создают дублированную среду. На мой взгляд (и я был бы рад ошибиться здесь), процесс не будет записывать какие-либо переменные сеанса (для чтения любым другим процессом), пока он не завершит выполнение. В качестве альтернативы, создание соединения через канал — это не то, что работает как отложенное обещание — когда вы можете вернуться к нему, когда есть вывод для чтения. - person Ricalsin; 15.04.2014
comment
Здесь я представляю вам session_write_close(), которая освобождает сеансовые блокировки от долго выполняющегося процесса. Вам придется повторно открыть сеанс, чтобы снова записать в него, но для этого есть кодовые решения: stackoverflow.com/questions/12315225/reopening-a-session-in-php - person Anatoliy Kim; 15.04.2014
comment
Интересная ссылка, но я думаю, что она доказывает, что такой взлом не лучше, чем знание/создание многопоточной среды. Кстати, вы представляете это как решение для исправления вашего исходного решения - нет? :) - person Ricalsin; 15.04.2014
comment
Нет, это просто пример того, как писать в сеанс после снятия блокировки. Кто знает, возможно, вы захотите закодировать его в стиле ООО с вашей структурой классов. Просто доказательство того, что это возможно. - person Anatoliy Kim; 15.04.2014
comment
Если вы не ограничены во времени или не нуждаетесь в идеальном решении - просто попробуйте сделать это таким образом. Если это сработает, то, конечно же, это избавит вас от необходимости создавать что-то более сложное? - person Anatoliy Kim; 15.04.2014