Начало » Для администраторов и программистов » Копилка » HTTP forking
HTTP forking Fri, 31 October 2008 03:03
San АНДРЕЕВ в настоящее время не в онлайне San АНДРЕЕВ  RUSSIAN FEDERATION
Сообщений: 9092
Зарегистрирован: June 2002
Географическое положение: Санкт-Петербу...
Легенда
administrator - partner
Я участвую в разработке www-системы с использованием технологии "клиент-сервер" на основе LAMP (P=PHP). В целом всё довольно просто: JS-приложение в броузере пользователя занимается запрашиванием у сервера всяких данных и/или действий и "красиво" оформляет результаты для пользователя.

Возникла задача распараллелить кое-какую деятельность на серверной стороне, с возможностью перевода этой самой деятельности в фон (т.е. с исключением ожидания завершения в www-броузере пользователя) с последующими опросом состояния и принудительным завершением при желании/необходимости.

Первое, что пришло в голову - воспользоваться встроенной PHP-функцией pcntl_fork(). Однако она и субъективно выглядит как-то чуждо в этом контексте, и объективно не всегда уместна, поскольку в данном случае может быть создана копия _всего_ текущего процесса www-сервера, который может содержать ряд обрабатывающих запросы клиентов потоков и всякие лишние для текущей задачи модули расширения.

Гугление навело на (не мою) идею форка через честный HTTP-запрос из первоначально вызываемого скрипта к своему же серверу (к тому же или к другому скрипту). Опять же субъективно этот подход хорош тем, что www-сервер сам обрабатывает соединение и решает сколько системных ресурсов ему под это выделить, какие модули расширения задействовать и т.д.

В процессе вышеупомянутого гугления примеры кода мне не попались (может гуглил плохо), поэтому "тезисно" опубликую свои наработки, в виде некоего каркаса.

Сначала каркас скрипта-родителя:
while ($i < $count) {
  // создаём сокет для общения со скриптом-потомком
  $socketToChild = fsockopen("localhost", 80);
  // строим HTTP-пакет; сначала заколовок
  $msgToChild = "POST /sript.php?&param=value&<more params> HTTP/1.0\n";
  $msgToChild .= "Host: localhost\n";
  $postData = "Какие-нибудь данные для потомка, будут переданы в виде POST-запроса";
  $msgToChild .= "Content-Length: ".strlen($postData)."\n\n";
  // заголовок построен, присоединяем сами данные
  $msgToChild .= $postData;
  // отправляем пакет на свой же www-сервер, в результате чего www-сервером
  // будет создан новый процесс/поток для обработки этого запроса
  fwrite($socketToChild, $msgToChild);
  // ждём и считываем ответ
  $data = fread($socketToChild, $dataSize);
  // закрываем соединение
  fclose($socketToChild);
  $i++;
}

Разумеется, это всё можно расширить и, например, придумать свой протокол более высокого уровня для обмена данными между родителем и потомком, что может потребовать вычитывания ответа в цикле и с подтверждением приёма и успешного распознания каждой порции данных. Цикл while(){} работает столько раз, сколько потомков необходимо породить.

Теперь каркас "ответной" части, скрипта-потомка:
// где-то до этого момента необходимо традиционными или
// какими-то ещё средствами разобрать HTTP-запрос на поля
// ----
// исключаем преждевременную выдачу ответа потомка "кусками",
// для чего включаем накопление, чтобы позже выдать всё за раз
ob_start();
// сообщаем среде выполнения о том, что нас не беспокоит отключение
// клиента (скрипта-родителя в данном случае) до завершения скрипта-потомка
ignore_user_abort(1);
// сообщаем среде выполнения о том, что мы можем работать сколь угодно долго
set_time_limit(0);
// здесь нужно что-нибудь ответить родителю, чтобы он отстал :)
// можно построить и отправить что-нибудь полезное, можно просто OK
...
echo $reply;
// выталкиваем из буфера накопленное содержимое (в данном случае - $reply) родителю
ob_flush();
// на всякий случай проделываем то же самое со всем имеющимися буферами
// не исключено, что в этом нет нужды - тут надо исследовать
flush();
// здесь родитель вычитывает наш ответ и сам закрывает соединение
// нам остаётся только отработать дальше и выполнить что было запланировано, уже в фоне
...

Для управления работой потомков необходимо сделать следующее:
- каждый потомок должен как-то сообщать родителю о своём состоянии;
- каждый потомок должен регулярно проверять наличие команд от родителя.

Снова приходили в голову всякие "системные" штуки вроде пайпов или разделяемой памяти, однако всё оказалось проще - раз уж используем LAMP (M=MySQL), то можно использовать БД и здесь, что я и проделал. После успешного рабора запроса родителя каждый потомок создаёт новую запись в БД о себе и возвращает родителю id этой записи, благодаря чему родитель теперь всегда имеет возможность наблюдать за состоянием конкретного потомка через периодические запросы к БД. Потомок же после каждого изменения своего состояния просто обновляет запись о себе в БД. Кроме того, эта же запись содержит и управляющие поля, "родительское" изменение значения в которых распознаётся потомком как команда на приостановку либо полное завершение работы (или ещё на что-нибудь). После того, как потомок всё выполнит (перейдёт в состояние "завершён") и соотв. образом изменит "свою" запись в БД, родитель может эту запись стереть, чтобы не раздувать БД.

Вот и всё.

P.S. Для обмена данными между родителем потомком необязательно использовать именно POST-запросы. Кроме того, в моём случае среди заголовков пакета к потомку присутствует ещё и Cookie для идентификации родителя.

[Обновления: Fri, 31 October 2008 03:09]

 
Сообщение не прочитано
Предыдущая тема:мнение живых людей о SLUB
Следующая тема:Монтирование нескольких ISO, чтобы виделось, как мультисессионный CD-ROM
Переход к форуму:
  


Текущее время: Mon May 20 08:51:20 MSK 2013
.:: Обратная связь :: Начало ::.

При поддержке: FUDforum 3.0.1.
Copyright © 2001-2010 FUDforum Bulletin Board Software