| HTTP forking |
Fri, 31 October 2008 03:03 |
San АНДРЕЕВ  Сообщений: 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?¶m=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]
|
|
|
|