Сообщений 0    Оценка 0        Оценить  
Система Orphus

Реализация средствами PHP: автономные сценарии

Глава из книги “Профессиональное программирование на PHP”

Автор: Джордж Шлосснейгл
Источник: Профессиональное программирование на PHP
Материал предоставил: Издательство ''Вильямс''
Опубликовано: 28.10.2006
Версия текста: 1.0
Введение в интерфейс командной строки PHP
Обработка ввода_вывода (I/O)
Разбор аргументов командной строки
Создание и управление дочерними процессами
Закрытие общих ресурсов
Совместное использование переменных
Чистка дочерних процессов
Сигналы
Написание демонов
Изменение рабочего каталога
Понижение привилегий
Обеспечение уникальности
Объединение полученных знаний:службы мониторинга
Дополнительная литература

В данной главе описано, как повторно использовать существующие библиотеки кода для осуществления административных задач в PHP и как писать автономне сценарии. В главе рассматриваются нетипичные проекты, в которых PHP используется вне Web-среды. Вероятно, один из наиболее захватывающих аспектов участия в разработке PHP заключается в наблюдении за тем, как данный язык вырастает из специфического языка для написания Web-сценариев времен PHP 3 (и более ранних версий) в надежный и многоцелевой язык, который также выделяется в разработке Web-сценариев. Для чрезвычайно специализированного языка характерны два преимущества:

С другой стороны, для чрезвычайно специализированного языка характерен и ряд недостатков:

Дублированный код означает, что ошибки необходимо устранять в нескольких местах (и еще хуже — на нескольких языках), что приравнивается к более высокому об щему уровню ошибок и тенденции расположения ошибок в менее используемых частях основания кода. Активная разработка на нескольких языках означает, что вместо того, чтобы разработчики становились экспертами в одном языке, они должны знать несколько. Это усложняет работу программистов, поскольку их внимание разделяется между множеством языков. В качестве альтернативы некоторые компании содержат отдельные группы программистов для работы в разных профессиональных областях. Такой подход может быть эффективным, но не решает проблему повторного использования кода; он дорог и снижает гибкость бизнеса.

ПРИМЕЧАНИЕ

Прагматизм

В своей превосходной книге The Pragmatic Programmer: From Journeyman to Master Дэвид Томас (David Thomas) и Эндрю Хант (Andrew Hunt) говорят о том, что все профессиональные программисты изучают (как минимум) один язык в год. С данной точкой зрения можно согласиться, но часто она плохо применима. Многие компании имеют крайне разрозненную и противоречивую программную базу с различными приложениями, написанными на разных языках, поскольку разработчик, который писал их, в то время изучал язык X и думал, что это будет хорошей возможностью отшлифовать свои навыки. Это наблюдается особенно часто, если ведущий разработчик в компании чрезвычайно талантлив или амбициозен и способен сравнительно легко работать с множеством языков. Проблема заключается в том, что, хотя ведущий программист может быть достаточно умным, чтобы работать одновременно с Python, Perl, PHP, Ruby, Java, C++ и C#, многие люди, которые будут работать с этой же программной базой, не способны справиться со множеством языков. В конце концов, накопится огромное количество повторяющегося кода. Например, непременно будет существовать одна базовая библиотека для доступа к базам данных, переписанная на каждом языке. Если разработчики удачливы и дальновидны, то все библиотеки будут иметь одинаковый API-интерфейс. Если же нет, то все интерфейсы будут отличаться, и появятся тысячи ошибок по мере того, как разработчики будут программировать Python API в PHP. Изучение новых языков — хорошая идея. К совету Томаса и Ханта стоит прислушаться. Изучение языков важно, потому что это расширяет горизонты, поддерживает актуальность навыков и раскрывает новые идеи. В работе следует использовать методики и знания, полученные в ходе изучения языков. В то же время следует сдержанно привносить в свою работу сами языки.

Для опытных программистов, вероятно, идеальным языком является тот, который имеет сходство с узкоспециальным языком в отношении центральных точек проектов, но в то же время достаточно универсален для решения возникающих периферийных задач. Для большинства потребностей Web-программирования PHP весьма точно соответствует данной роли. Модель PHP-разработки остается близкой к корням данного языка, связанным с Web-сценариями. По простоте использования и соответствию “проблеме Web” данный язык до сих пор не имеет аналогов (что подтверждается постоянным ростом числа его приверженцев). PHP также адаптировался к решению более универсальных проблем. Начиная с PHP 4, а также в PHP 5 язык стал соответствовать большому количеству задач, не связанных с Web-программированием. Является ли PHP лучшим языком для решения серверных задач? Если в компании используется развитый API(интерфейс, управляющий множеством бизнес(процессов, то возможность слияния и повторного использования кода из Web-среды компании особенно ценна. Эта ценность может легко превзойти тот факт, что Perl и Python являются более зрелыми языками написания серверных сценариев.

Введение в интерфейс командной строки PHP

Если PHP компилируется с параметром --enable-cli, то бинарный файл php устанавливается в каталог бинарных файлов инсталляционного пути. По умолчанию данным каталогом является /usr/local/bin. Для того чтобы избежать необходимости указывать полный путь к php при каждом запуске сценариев, данный каталог должен присутствовать в переменной среды PATH. Чтобы вызвать сценарий phpscript.php из командной строки Unix(системы, можно ввести такую команду:

> php phpscript.php

В качестве альтернативы можно добавить в начало сценария следующую строку:

#!/usr/bin/env php

а затем сделать данный сценарий исполняемым, изменив права доступа к нему с помощью команды chmod:

> chmod u+rx PHPscript.php

Теперь phpscript.php можно вызвать следующим образом:

> ./phpscript.php

Использование синтаксиса #! — является стандартный способ создавать исполняемые сценарии в Unix-системах.

На Windows(системах для сопоставления .php-сценариев с исполняемым файлом php модифицируется реестр, так чтобы система после щелчка на имени файла могла проанализировать и выполнить его. Однако так как PHP на Unix-системах распространен шире (главным образом по причине безопасности, стоимости и производительности),чем на Windows-системах, в данной книге рассматриваются исключительно Unix-примеры.

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

Обработка ввода_вывода (I/O)

Центральным аспектом философии проектирования Unix является то, что для решения сложных задач небольшие и независимые программы можно объединять в цепочки. Создание таких цепочек обычно возможно благодаря тому, что программа считывает ввод с терминала и отправляет вывод обратно в терминал. Unix-среда предоставляет три специальных дескриптора файла, которые можно использовать для отправки и получения данных между приложением и вызывающим его пользовательским терминалом (также называемым tty).

В интерфейсе командной строки (command(line interface, CLI) PHP специальные дескрипторы файлов доступны с помощью следующих констант:

Использование этих констант идентично открытию соответствующих потоков вручную. (Если используется версия PHP CGI, то это необходимо делать вручную.)

Потоки открываются явно с помощью таких вызовов:

$stdin = fopen("PHP://stdin", "r");
$stdout = fopen("PHP://stdout", "w");
$stderr = fopen("PHP://stderr", "w");
ПРИМЕЧАНИЕ

Зачем использовать STDOUT?

Использование STDOUT в качестве дескриптора файла, когда можно непосредственно печатать с помощью print/echo, может показаться нецелесообразным. Несмотря на это, на практике такой подход весьма удобен. STDOUT позволяет писать функции вывода, которые просто принимают ресурсы потоков, так что можно легко переключаться между отправкой вывода на пользовательский терминал и отправкой вывода удаленному серверу через HTTP-поток или другому получателю через иной поток вывода. Недостаток заключается в том, что невозможно получить преимущества фильтров вывода или буферизации вывода в PHP, но можно с помощью функции streams_filter_register()зарегистрировать собственные фильтры потоков.

Ниже приведен простой сценарий, который считывает файл с stdin, нумерует каждую строку данного файла и выводит результат на stdout:

#!/usr/bin/env php
<?php
$lineno = 1;
while(($line = fgets(STDIN)) != false) {
   fputs(STDOUT, "$lineno $line");
   $lineno++;
}
?>

Если запустить данный сценарий для себя самого, то получится следующий вывод:

1 #!/usr/bin/env php
2 <?php
3
4 $lineno = 1;
5 while(($line = fgets(STDIN)) != false) {
6 fputs(STDOUT, "$lineno $line");
7 $lineno++;
8 }
9 ?>

Поток stderr удобно использовать для уведомления об ошибках и отладки, поскольку он не будет считываться в stdin принимающей программы. Ниже приведена программа, которая считывает log-файл Apache комбинированного формата и выводит отчет по количеству уникальных IP-адресов и типов браузеров, записанных в файле:

<?PHP
$counts = array( 'ip' = > array(), 'user_agent' = > array() );

while ( ( $line = fgets( STDIN ) ) != false )
{
# Данное регулярное выражение позволяет поле за
# полем сверять форматированную строку журнала.
   $regex = '/^(\S+) (\S+) (\S+) \[([^:]+):(\d+:\d+:\d+) ([^\]]+)\] '.
            '"(\S+) (.*?) (\S+)" (\S+) (\S+) "([^"]*)" "([^"]*)"$/';
   preg_match( $regex, $line, $matches );
   list( , $ip, $ident_name, $remote_user, $date, $time,
         $gmt_off, $method, $url, $protocol, $code,
         $bytes, $referrer, $user_agent ) = $matches;
   $counts[ 'ip' ][ "$ip" ] ++;
   $counts[ 'user_agent' ][ "$user_agent" ] ++;
# Печатать '.' в STDERR через каждую тысячу обработанных строк.

   if ( ( $lineno++ % 1000 ) == 0 )
   {
      fwrite( STDERR, "." );
   }
}

arsort( $counts[ 'ip' ], SORT_NUMERIC );
reset( $counts[ 'ip' ] );
arsort( $counts[ 'user_agent' ], SORT_NUMERIC );
reset( $counts[ 'user_agent' ] );
foreach( array( 'ip', 'user_agent' ) as $field )
{
   $i = 0;
   print "Максимальное количество запросов по $field\n";
   print "--------------------------------\n";
   foreach( $counts[ $field ] as $k = > $v )
   {
      print "$v\t\t$k\n";

      if ( $i++ == 10 )
      {
         break;
      }
   }

   print "\n\n";
}
?>

Сценарий работает, считывая log-файл в STDIN и сверяя каждую строку с $regex для извлечения отдельных полей. Затем подсчитывается суммарная статистика, количество запросов на уникальный IP-адрес и для каждого уникального браузера. Поскольку log-файлы комбинированного формата велики, можно отправлять в stderr точку (.) через каждую тысячу строк, чтобы отразить ход выполнения разбора. Если вывод сценария перенаправляется в файл, то в данном файле появится только окончание отчета, а точки будут появляться только на пользовательском экране.

Разбор аргументов командной строки

Когда PHP-сценарий запускается из командной строки, невозможно передать ему аргументы через переменные $_GET и $_POST (CLI-интерфейс “не имеет понятия” об этих Web-протоколах). Вместо этого аргументыпередаются в командную строку. Аргументы командной строки можно считывать необработанными из автоглобальной переменной $argv.

Сценарий

#!/usr/bin/env php
<?PHP
print_r($argv);
?>

при вызове

> ./dump_argv.php foo bar barbara

отобразит такой вывод:

Array
(
[0] => dump_argv.php
[1] => foo
[2] => bar
[3] => barbara
)

Следует отметить, что элемент $argv[0] содержит имя выполняемого сценария. Непосредственное использование параметров из $argv может быть бесполезным, поскольку требует ввода параметров в определенном порядке. Более надежным вариантом, чем анализ параметров вручную, является использование PEAR-пакета Console_Getopt. Пакет Console_Getopt предоставляет простой в использовании интерфейс для распределения параметров командной строки в удобный для обработки массив. В дополнение к этому Console_Getopt обрабатывает как длинные, так и короткие ключи, а также обеспечивает базовую проверку, гарантирующую, что параметры переданы в корректном формате.

Класс Console_Getopt работает, принимая форматированные строки для ожидаемых аргументов. Параметры могут передаваться в двух формах: с длинными ключами и с короткими ключами.

Короткие ключи — однобуквенные ключи с необязательными данными. Спецификатором формата для коротких ключей является строка допустимых лексем. За буквой ключа может следовать двоеточие (:), указывающее на то, что данный ключ требует параметра, или два двоеточия (::), чтобы указать, что параметр необязателен.

Длинные ключи — массив многобуквенных ключевых слов (например, --help). Чтобы указать, что данный ключ принимает параметр, за строкой ключа может следовать один знак равенства (=); два знака равенства (==) указывают на то, что параметр необязателен.

Например, для сценария, который должен принимать флаги -h и --help без параметров, а флаг --file — с обязательным параметром, следует использовать такой код:

require_once "Console/Getopt.php";
$shortoptions = "h";
$longoptons = array("file=", "help");
$con = new Console_Getopt;
$args = Console_Getopt::readPHPArgv();
$ret = $con->getopt($args, $shortoptions, $longoptions);

Возвращаемым значением функции getopt() является массив, содержащий двумерный массив. Первый внутренний массив содержит аргументы коротких ключей, а второй содержит аргументы длинных ключей. Console_Getopt::readphpARGV() — внеконфигурационный способ получения данных $argv (например, если директива register_argc_argv в файле php.ini установлена в off).

Обычный вывод getopt() несколько сложен. Можно представлять параметры как один ассоциативный массив пар ключ/значение, с символами флага в качестве ключа и значением параметра в качестве значения массива. Следующий блок кода использует Console_Getopt для получения такого эффекта:

function getOptions( $default_opt, $shortoptions, $longoptions )
{
   require_once "Console/Getopt.php";
   $con = new Console_Getopt;
   $args = Console_Getopt::readPHPArgv();
   $ret = $con->getopt( $args, $shortoptions, $longoptions );
   $opts = array();
   foreach( $ret[ 0 ] as $arr )
   {
      $rhs = ( $arr[ 1 ] != = null ) ? $arr[ 1 ] : true;

      if ( array_key_exists( $arr[ 0 ], $opts ) )
      {
         if ( is_array( $opts[ $arr[ 0 ] ] ) )
         {
            $opts[ $arr[ 0 ] ][] = $rhs;
         }
         else
         {
            $opts[ $arr[ 0 ] ] = array( $opts[ $arr[ 0 ] ], $rhs );
         }
      }
      else
      {
         $opts[ $arr[ 0 ] ] = $rhs;
      }
   }

   if ( is_array( $default_opt ) )
   {
      foreach ( $default_opt as $k = > $v )
      {
         if ( !array_key_exists( $k, $opts ) )
         {
            $opts[ $k ] = $v;
         }
      }
   }

   return $opts;
}

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

Используя данную функцию, можно переделать пример с ключом --help:

$shortoptions = "h";
$longoptions = array("file=", "help");
$ret = getOptions(null, $shortoptions, $longoptions);

Если вызвать данный код с параметрами -h --file=error.log, то массив $ret будет иметь следующую структуру:

Array
(
[h] => 1
[--file] => error.log
)

Создание и управление дочерними процессами

В PHP отсутствует собственная поддержка потоков (threads). Для разработчиков, перешедших к PHP с языков, ориентированных на потоки, таких как Java, это усложняет написание программ, которые одновременно должны выполнять множество задач. Однако в PHP поддерживается традиционная многозадачность Unix через возможность порождения дочерних процессов с помощью функции pcntl_fork() (оболочки для системного вызова Unix fork()). Чтобы включить данную функцію (и все функции pcntl_*), необходимо скомпилировать PHP с флагом --enable-pcntl.

Когда в сценарии вызывается функция pcntl_fork(), создается новый процесс, который продолжает выполнение сценария с точки вызова pcntl_fork(). Исходный процесс также продолжает выполнение с данной точки. Это означает, что тепер существует две запущенные копии сценария — родительская (исходный процесс) и дочерняя (вновь созданный процесс).

Функция pcntl_fork() фактически возвращает значение дважды: первый раз — в родительский процесс и второй раз — в дочерний. В родительский процесс возвращается идентификатор вновь созданного процесса (process ID или PID). Значение, возвращаемое в дочерний процесс, — 0. Это позволяет различить родительский и дочерний процессы.

В следующем простом сценарии создается дочерний процесс:

#!/usr/bin/env php
<?PHP
if($pid = pcntl_fork()) {
   $my_pid = getmypid();
   print "Мой pid - $my_pid. pcntl_fork() возвращает $pid, это родительский процесс\n";
} else {
   $my_pid = getmypid();
   print "Мой pid - $my_pid. pcntl_fork() возвратила 0, это дочерний процесс\n";
}
?>

При выполнении данного сценария генерируется вывод:

> ./4.php
Мой pid - 4286. pcntl_fork() возвращает 4287, это родительский процесс
Мой pid - 4287. pcntl_fork() возвратила 0, это дочерний процесс

Следует отметить, что значение, возвращаемое pcntl_fork(), действительно совпадает с PID дочернего процесса. Кроме того, запустив данный сценарий несколько раз, можно заметить, что иногда первым печатается вывод родительского сценария, а иногда — дочернего. Ввиду того что эти сценарии выполняются в отдельных процессах, им отводятся процессорные ресурсы в том порядке, в каком операционная система высвобождает эти ресурсы, а не на основе отношений родитель–потомок.

Закрытие общих ресурсов

Когда в Unix-среде происходит ответвление процесса, как родительский, так и дочерний процессы имеют доступ к любым файловым ресурсам, которые открыты на момент вызова fork(). Для совместного использования ресурсов процессами это не так удобно, как может показаться. Поскольку управляющие механизмы, предотвращающие одновременный доступ к ресурсам, отсутствуют, результирующий ввод-вывод часто перемешивается. Для файлового ввода-вывода это обычно проявляется в беспорядочном смешении строк. Для сложного ввода-вывода сокетов, как в случае соединений с базами данных, это часто приводит к фатальному сбою процесса.

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

Совместное использование переменных

Необходимо помнить: порожденные pcntl_fork() процессы отличаются от потоков. Процессы, созданные с помощью данной функции, являются индивидуальными, и изменения переменных в одном процессе после разветвления не отражаются на переменных в других процессах. Если требуется обеспечить совместное использование переменных процессами, то можно либо хранить переменные, используя расширения для работы с общей памятью, либо воспользоваться приемом “tie” (см. главу 2, “Объектно-ориентированное программирование с использованием типовых проектов”).

Чистка дочерних процессов

В Unix-среде “умершим” является процесс, который завершил работу, но это состояние не определено его предком. Определение состояния потомка, после которого его можно удалить из системы, также называется аннулированием (reaping) или чист_кой дочернего процесса. Ответственный родительский процесс всегда аннулируетсвоих потомков.

PHP предоставляет два способа обработки завершения дочерних процессов.

Ниже приведен пример процесса, который запускает заданное количество дочерних процессов и ждет их завершения:

#!/usr/bin/env php
<?PHP
define( 'PROCESS_COUNT', '5' );
$children = array();

for ( $i = 0; $i < PROCESS_COUNT; $i++ )
{
   if ( ( $pid = pcntl_fork() ) == 0 )
   {
      exit( child_main() );
   }
   else
   {
      $children[] = $pid;
   }
}

foreach( $children as $pid )
{
   $pid = pcntl_wait( $status );

   if ( pcntl_wifexited( $status ) )
   {
      $code = pcntl_wexitstatus( $status );
      print "Процесс $pid возвратил код выхода: $code\n";
   }
   else
   {
      print "Процесс $pid был неестественно уничтожен\n";
   }
}

function child_main()
{
   $my_pid = getmypid();
   print "Запуск дочернего процесса: $my_pid\n";
   sleep( 10 );
   return 1;
}
?>

Стоит отметить один аспект данного примера: весь код, который должен выполняться дочерним процессом, расположен в функции child_main(). В данном примере выполняется только sleep(10), но можно изменить код для использования более сложной логики.

Кроме того, когда дочерний процесс завершается и вызов к pcntl_wait() возвращает значение, с помощью функции pcntl_wifexited() можно проверить состояние, чтобы определить, как завершен дочерний процесс — с помощью вызова exit() или его завершение было неестественным. Если завершение связано с выходом сценария, то с помощью вызова pcntl_wexitstatus($status) можно извлечь фактический код, переданный функции exit(). Коды состояния выхода представляют собой 8-битовые числа со знаком в диапазоне от –127 до 127.

Ниже приведен вывод данного сценария, работающего непрерывно:

> ./5.php
Запуск дочернего процесса: 4451
Запуск дочернего процесса: 4452
Запуск дочернего процесса: 4453
Запуск дочернего процесса: 4454
Запуск дочернего процесса: 4455
Процесс 4453 возвратил код выхода: 1
Процесс 4452 возвратил код выхода: 1
Процесс 4451 возвратил код выхода: 1
Процесс 4454 возвратил код выхода: 1
Процесс 4455 возвратил код выхода: 1

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

> ./5.php
Запуск дочернего процесса 4459
Запуск дочернего процесса 4460
Запуск дочернего процесса 4461
Запуск дочернего процесса 4462
Запуск дочернего процесса 4463
Процесс 4462 был неестественно уничтожен
Процесс 4463 возвратил код выхода: 1
Процесс 4461 возвратил код выхода: 1
Процесс 4460 возвратил код выхода: 1
Процесс 4459 возвратил код выхода: 1

Сигналы

Сигналы отправляют простые инструкции процессам. Когда для прерывания процесса в системе используется shell(команда kill, отправляется сигнал прерывания (SIGINT). Большинство сигналов имеет стандартный режим работы (например, стандартный режим для сигнала SIGINT — уничтожить процесс), но, за некоторым исключением, сигналы могут быть перехвачены и обработаны нестандартными способами внутри процесса.

Ниже перечислены некоторые из наиболее распространенных сигналов (полныйсписок находится в man(странице signal(3)).

Имя сигналаОписаниеСтандартное действие
SIGCHLDЗавершение работы потомкаИгнорировать
SIGINTЗапрос прерыванияЗавершение процесса
SIGKILLОстанов программыЗавершение процесса
SIGHUPОтключение терминалаЗавершение процесса
SIGUSR1Определяется пользователемЗавершение процесса
SIGUSR2Определяется пользователемЗавершение процесса
SIGALRMТайм-аутЗавершение процесса

Для того чтобы зарегистрировать собственный обработчик сигнала, не обходимо просто определить функцию:

function sig_usr1($signal)
{
   print "Перехват SIGUSR1.\n";
}

а затем зарегистрировать ее:

declare(ticks=1);
pcntl_signal(SIGUSR1, "sig_usr1");

Поскольку сигналы работают на уровне процесса, а не внутри самой виртуальной машины PHP, ядро разрабатываемой системы необходимо заставить сличать сигналы и запускать обратные вызовы pcntl. Чтобы разрешить это, необходимо установить директиву выполнения ticks. ticks указывает ядру выполнять определенные обратные вызовы каждые N операторов в исполнителе. Обратный вызов сигнала, в сущности, является холостой командой, поэтому установка declare(ticks=1) предписывает ядру искать сигналы при выполнении каждого оператора.

В следующем разделе описаны два наиболее полезных обработчика сигналов для многопроцессных сценариев — SIGCHLD и SIGALRM, а также других распространенных сигналов.

SIGCHLD

SIGCHLD — распространенный обработчик сигналов, который устанавливается в приложениях там, где ответвляются дочерние процессы. В примерах из предыдущего раздела родительский процесс должен в цикле выполнять функции pcntl_wait() или pcntl_waitpid(), чтобы гарантировать, что все потомки аннулированы. Сигналы обеспечивают способ уведомления родительского процесса в случае завершения дочернего процесса о том, что дочерние процессы должны быть аннулированы. Благодаря этому родительский процесс может выполнять собственную логику, а не просто циклы в ожидании очистки потомков.

Чтобы реализовать такую схему, сначала необходимо определить обратный вызов для обработки событий SIGCHLD. Ниже приведен простой пример, в котором из глобального массива $children удаляется PID и распечатывается отладочная информация о том, что происходит.

function sig_child($signal)
{
   global $children;
   pcntl_signal(SIGCHLD, "sig_child");
   fputs(STDERR, "Перехвачен SIGCHLD\n");
   while(($pid = pcntl_wait($status, WNOHANG)) > 0) {
      $children = array_diff($children, array($pid));
      fputs(STDERR, "Аннулирован процесс $pid\n");
   }
}

Сигнал SIGCHLD не сообщает ничего о том, какой процесс был прерван, потому необходимо внутренне вызывать pcntl_wait(), чтобы найти прерванные процессы. Поскольку при вызове обработчика сигналов могут завершиться многие процессы, функцию pcntl_wait() необходимо выполнять в цикле до тех пор, пока не останется завершенных процессов, чтобы гарантировать, что все они аннулированы. Поскольку используется параметр WNOHANG, вызов не заблокирует родительский процесс.

Большинство современных средств обработки сигналов восстанавливает обработчик сигнала после вызова, но для переносимости на более старые системы следует всегда восстанавливать обработчик сигнала вручную внутри вызова. После добавления обработчика SIGCHLD к предыдущему примеру получим следующий код:

#!/usr/bin/env php
<?PHP
declare( ticks = 1 );
pcntl_signal( SIGCHLD, "sig_child" );
define( 'PROCESS_COUNT', '5' );
$children = array();

for ( $i = 0; $i < PROCESS_COUNT; $i++ )
{
   if ( ( $pid = pcntl_fork() ) == 0 )
   {
      exit( child_main() );
   }
   else
   {
      $children[] = $pid;
   }
}

while ( $children )
{
   sleep( 10 ); // или выполнить логику родительского процесса
}

pcntl_alarm( 0 );
function child_main()
{
   sleep( rand( 0, 10 ) ); // или выполнить логику дочернего процесса
   return 1;
}

function sig_child( $signal )
{
   global $children;
   pcntl_signal( SIGCHLD, "sig_child" );
   fputs( STDERR, "Перехвачен SIGCHLD\n" );

   while ( ( $pid = pcntl_wait( $status, WNOHANG ) ) > 0 )
   {
      $children = array_diff( $children, array( $pid ) );

      if ( !pcntl_wifexited( $status ) )
      {
         fputs( STDERR, "Аннулирован уничтоженный процесс $pid\n" );
      }
      else
      {
         fputs( STDERR, "Аннулирован завершенный процесс $pid\n" );
      }
   }
}
?>

Выполнение данного кода приведет к отображению следующего вывода:

> ./8.php
Перехвачен SIGCHLD
Аннулирован завершенный процесс 5000
Перехвачен SIGCHLD
Аннулирован завершенный процесс 5003
Перехвачен SIGCHLD
Аннулирован завершенный процесс 5001
Перехвачен SIGCHLD
Аннулирован завершенный процесс 5002
Перехвачен SIGCHLD
Аннулирован завершенный процесс 5004

SIGALRM

Полезным также является SIGALRM, сигнал таймера. Таймеры позволяют освободить ресурсы от выполнения задач, если их завершение занимает слишком много времени. Для использования таймера определяется и регистрируется обработчик сигналов, а затем вызывается функция pcntl_alarm() для установки тайм(аута. Когда достигается значение заданного тайм-(аута, процессу отправляется сигнал SIGALRM.

Ниже приведен обработчик сигналов, который просматривает в цикле все PID-идентификаторы, оставшиеся в $children, и отправляет им сигнал SIGINT (то же, что и команда kill в оболочке Unix).

function sig_alarm($signal)
{
   global $children;
   fputs(STDERR, "Перехвачен SIGALRM\n");
   foreach ($children as $pid) {
      posix_kill($pid, SIGINT);
   }
}

Необходимо отметить использование функции posix_kill(). posix_kill() отправляет указанному процессу заданный сигнал.

Также необходимо зарегистрировать sig_alarm(), обработчик SIGALRM (наряду с обработчиком SIGCHLD) и изменить главный блок:

declare(ticks=1);
pcntl_signal(SIGCHLD, "sig_child");
pcntl_signal(SIGALRM, "sig_alarm");
define('PROCESS_COUNT', '5');
$children = array();
pcntl_alarm(5);
for($i = 0; $i < PROCESS_COUNT; $i++) {
   if(($pid = pcntl_fork()) == 0) {
      exit(child_main());
   }
   else {
      $children[] = $pid;
   }
}
while($children) {
   sleep(10); // или выполнить логику родительского процесса
}
pcntl_alarm(0);

Важно не забывать устанавливать тайм-аут таймера в 0, когда он не нужен. В противном случае он активизируется в самый неожиданный момент. Выполнение модифицированного сценария приведет к отображению следующего вывода:

> ./9.php
Перехвачен SIGCHLD
Аннулирован завершенный процесс 5011
Перехвачен SIGCHLD
Аннулирован завершенный процесс 5013
Перехвачен SIGALRM
Перехвачен SIGCHLD
Аннулирован уничтоженный процесс 5014
Аннулирован уничтоженный процесс 5012
Аннулирован уничтоженный процесс 5010

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

Другие широко распространенные сигналы

Другими распространенными сигналами, для которых могут понадобиться обработчики, являются SIGHUP, SIGUSR1 и SIGUSR2. Стандартным поведением для процесса при получении любого из этих сигналов является завершение работы. SIGHUP — сигнал, отправляемый при отсоединении терминала (при выходе из оболочки). Обычный процесс, работающий в фоновом режиме, уничтожается, когда пользователь завершает терминальный сеанс.

Если необходимо заставить сценарий просто проигнорировать сигналы, то это можно сделать с помощью следующего кода:

pcntl_signal(SIGHUP, SIGIGN);

Вместо того чтобы игнорировать эти три сигнала, их обычно используют для отправки процессам простых команд — например, заново считать конфигурационный файл, заново открыть log(файл или вывести информацию о состоянии.

Написание демонов

Демон (daemon) — процесс, выполняющийся в фоновом режиме. Это означает, что с момента запуска демон не принимает никакого ввода с пользовательского терминала и не завершает свою работу при завершении пользовательского сеанса. Однажды запущенные демоны обычно работают постоянно (или до тех пор, пока не будут остановлены) для выполнения периодически повторяющихся задач или для обработки задач, выполнение которых может продолжаться дольше пользовательского сеанса. Web-сервер Apache, sendmail и демон crond — примеры распространенных демонов, которые могут выполняться в Unix(системе. Создание сценариев-демонов полезно для обработки длинных заданий и периодически повторяющихся серверных задач.

Для того чтобы успешно стать демоном, процессу необходимо выполнить две задачи:

Хорошо написанные демоны могут, хотя и не обязательно, выполнять следующие задачи:

Отсоединение процесса рассматривалось в настоящей главе выше, в разделе “Создание и управление дочерними процессами”. Логика в данном случае та же, что и для преобразования процесса в демон, кроме того, что требуется завершить роди тельский процесс так, чтобы только запускающийся процесс отсоединялся от shell. Для этого процесс вызывает функцию pnctl_fork() и завершает свою работу, если он является родительским (то есть, если возвращаемое значение больше нуля).

В Unix(системах процессы связаны с группами процессов, поэтому, когда уничтожается главный процесс (лидер) группы, уничтожаются также все связанные с ним процессы. Родительским процессом для всех процессов, запущенных пользователем в оболочке, является процесс оболочки. Таким образом, если просто создать новый процесс с помощью вызова fork(), то он завершит работу при закрытии оболочки. Для того чтобы избежать этого, необходимо, чтобы ответвленный процесс разорвал связь со своим предком. Это достигается путем вызова функции pcntl_setsid(), которая делает вызывающий процесс лидером его собственной группы процессов.

Наконец, чтобы разорвать связи между родительским и дочерним процессом, необходимо во второй раз осуществить ветвление процесса. Это завершает процесс отсоединения. В коде отсоединение выглядит так:

if(pcntl_fork()) {
   exit;
}
pcntl_setsid();
if(pcntl_fork()) {
   exit;
}
# теперь процесс полностью преобразован в демон

Важно, чтобы родительский процесс завершился после обоих вызовов pcntl_fork(); в противном случае несколько процессов будут выполнять один и тот же код.

Изменение рабочего каталога

Обычно рекомендуется писать демон так, чтобы он устанавливал свой рабочий каталог. В таком случае при считывании или записи файлов через относительный путь данные файлы будут находиться там, где ожидается. Определение путей при каждом запуске демона, несомненно, является хорошей практикой, кроме того, такое программирование является безопасным. Самый безопасный способ изменениярабочего каталога — использование не только функции chdir(), но и функции chroot(). Функция chroot() доступна внутри CLI( и CGI(версии PHP и требует, чтобы программа выполнялась от имени root. chroot() изменяет корневой каталог процесса на указанный. Это делает невозможным выполнение любых файлов, которые находятся не внутри данного каталога. chroot() часто используется серверами как безопасное средство, гарантирующее, что злонамеренный код не сможет изменить файлы за пределами указанного каталога. Необходимо помнить: несмотря на то что chroot() предотвращает доступ к файлам за пределами нового каталога, открытые в текущий момент файловые ресурсы остаются доступными. Например, следующий код открывает log-файл, вызывает функцию chroot() для переключения на каталог с данными и, несмотря на это, может успешно осуществлять запись в открытый файловый ресурс.

<?PHP
$logfile = fopen("/var/log/chroot.log", "w");
chroot("/Users/george");
fputs($logfile, "Запись после Chroot\n");
?>

Если функцию chroot() нежелательно использовать в приложении, то для установки рабочего каталога можно вызвать chdir(). Это полезно, например, когда требуется загрузить код, который может быть расположен в системе где угодно. Следует отметить, что chdir() не предоставляет способа для предотвращения несанкционированного открытия файлов — только символическая защита против небрежногопрограммирования.

Понижение привилегий

Классической мерой предосторожности при написании Unix(демонов является понижение всех ненужных привилегий. Как и возможность доступа к файлам за пределами определенного каталога, владение излишними привилегиями чревато неприятностями. Если код (или сама машина PHP) имеет недостаток, который можно использовать в злонамеренных целях, свести повреждение к минимуму можно, когда демон работает от имени пользователя с минимальными правами на изменение файлов в системе.

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

Если программа выполняется от имени root, то понизить привилегии можно, используя функции posix_setuid() и posiz_setgid(). Ниже приводится пример, в котором привилегии работающей программы изменяются на привилегии пользователя nobody.

$pw= posix_getpwnam('nobody');
posix_setuid($pw['uid']);
posix_setgid($pw['gid']);

Как и в случае chroot(), привилегированные ресурсы, которые были открыты до понижения привилегий программы, остаются открытыми, но создание новых — невозможно.

Обеспечение уникальности

Часто требуется, чтобы в любой момент времени выполнялся только один экземпляр сценария. Это особенно важно для сценариев(демонов, поскольку работа в фоновом режиме упрощает случайный вызов нескольких экземпляров.

Стандартным приемом для того, чтобы гарантировать уникальность, является блокировка сценарием с помощью функции flock() определенного файла (часто файла блокировки (lockfile), который используется исключительно для этой цели). Если блокировка невозможна, сценарий должен завершиться с ошибкой; см. пример ниже.

$fp = fopen("/tmp/.lockfile", "a");
if(!$fp || !flock($fp, LOCK_EX | LOCK_NB)) {
   fputs(STDERR, "Блокировка невозможна\n");
   exit;
}
/* файл заблокирован; выполнение работы безопасно */

Механизмы блокировки более подробно обсуждаются в главе 10, “Кэширование данных”.

Объединение полученных знаний:службы мониторинга

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

Регистрирующая программа должна иметь возможность поддерживать произвольные проверки служб (например, HTTP и FTP(служб), а также протоколировать события произвольными способами (посредством email, в log(файл и т.д.). Естественно, система должна работать как демон, чтобы была возможность получать по запросу полные сведения о ее текущем состоянии.

Служба должна реализовывать следующий абстрактный класс:

abstract class ServiceCheck
{
      const FAILURE = 0;
      const SUCCESS = 1;
      protected $timeout = 30;
      protected $next_attempt;
      protected $current_status = ServiceCheck::SUCCESS;
      protected $previous_status = ServiceCheck::SUCCESS;
      protected $frequency = 30;
      protected $description;
      protected $consecutive_failures = 0;
      protected $status_time;
      protected $failure_time;
      protected $loggers = array();
      abstract public function __construct( $params );
      public function __call( $name, $args )
      {
         if ( isset( $this->$name ) )
         {
            return $this->$name;
         }
      }

      public function set_next_attempt()
      {
         $this->next_attempt = time() + $this->frequency;
      }

      public abstract function run();
      public function post_run( $status )
      {
         if ( $status != = $this->current_status )
         {
            $this->previous_status = $this->current_status;
         }

         if ( $status == = self::FAILURE )
         {
            if ( $this->current_status == = self::FAILURE )
            {
               $this->consecutive_failures++;
            }
            else
            {
               $this->failure_time = time();
            }
         }
         else
         {
            $this->consecutive_failures = 0;
         }

         $this->status_time = time();
         $this->current_status = $status;
         $this->log_service_event();
      }
      public function log_current_status()
      {
         foreach( $this->loggers as $logger )
         {
            $logger->log_current_status( $this );
         }
      }
      private function log_service_event()
      {
         foreach( $this->loggers as $logger )
         {
            $logger->log_service_event( $this );
         }
      }
      public function register_logger( ServiceLogger $logger )
      {
         $this->loggers[] = $logger;
      }
}

Переопределяемый метод __call() предоставляет доступ только для чтения к следующим параметрам ServiceCheck-объекта:

Класс также реализует типовой проект Observer (наблюдатель), позволяющий объектам типа ServiceLogger регистрироваться и впоследствии вызываться каждый раз при вызове функций log_current_status() или log_service_event().

Важнейшей функцией, которую необходимо реализовать, является run(), определяющая способ запуска проверки. Она должна возвращать значение SUCCESS, если проверка удачна, и FAILURE, если это не так.

Метод post_run() вызывается после проверки службы, определенной в run(). Метод обрабатывает установку состояния объекта и выполняет протоколирование.

Интерфейс ServiceLogger определяет, что класс протоколирования должен реализовать только два метода — log_service_event() и log_current_status(), которые вызываются, когда возвращается значение run()-проверки и когда сделан запрос на общее состояние, соответственно.

Ниже приведен код интерфейса.

interface ServiceLogger {
   public function log_service_event(ServiceCheck $service);
   public function log_current_status(ServiceCheck $service);
}

Наконец, необходимо написать само ядро. Идея в данном случае подобна тем, что лежат в основе простых программ, описанных в разделе “Написание демонов” настоящей главы: сервер должен ответвить новый процесс для обработки каждой проверки и использовать обработчик SIGCHLD для проверки возвращаемого значения проверок после их завершения. Максимальное число одновременно выполняемых проверок должно быть конфигурируемым, чтобы предотвратить чрезмерное потребление системних ресурсов. Все службы и протоколирование будут определяться в XML-файле.

Ниже приводится код класса ServiceCheckRunner, определяющего ядро.

class ServiceCheckRunner
{
      private $num_children;
      private $services = array();
      private $children = array();
      public function __construct( $conf, $num_children )
      {
         $loggers = array();
         $this->num_children = $num_children;
         $conf = simplexml_load_file( $conf );
         foreach( $conf->loggers->logger as $logger )
         {

            $class = new ReflectionClass( "$logger->class" );

            if ( $class->isInstantiable() )
            {

               $loggers[ "$logger->id" ] = $class->newInstance();
            }
            else
            {
               fputs( STDERR, "Невозможно создать объект класса
                   {$logger->class}.\n" );
               exit;
            }
         }

         foreach( $conf->services->service as $service )
         {

            $class = new ReflectionClass( "$service->class" );

            if ( $class->isInstantiable() )
            {

               $item = $class->newInstance( $service->params );
               foreach( $service->loggers->logger as $logger )
               {
                  $item->register_logger( $loggers[ "$logger" ] );
               }

               $this->services[] = $item;
            }
            else
            {
               fputs( STDERR, "Объект класса {$service->class} не создается.\n" );
               exit;
            }
         }
      }

      private function next_attempt_sort( $a, $b )
      {
         if ( $a->next_attempt() == $b->next_attempt() )
         {
            return 0;
         }

         return ( $a->next_attempt() < $b->next_attempt() ) ? -1 : 1;
      }

      private function next()
      {
         usort( $this->services, array( $this, 'next_attempt_sort' ) );
         return $this->services[ 0 ];
      }

      public function loop()
      {
         declare( ticks = 1 );
         pcntl_signal( SIGCHLD, array( $this, "sig_child" ) );
         pcntl_signal( SIGUSR1, array( $this, "sig_usr1" ) );

         while ( 1 )
         {
            $now = time();

            if ( count( $this->children ) < $this->num_children )
            {
               $service = $this->next();

               if ( $now < $service->next_attempt() )
               {
                  sleep( 1 );
                  continue;
               }

               $service->set_next_attempt();

               if ( $pid = pcntl_fork() )
               {
                  $this->children[ $pid ] = $service;
               }
               else
               {
                  pcntl_alarm( $service->timeout() );
                  exit( $service->run() );
               }
            }
         }
      }

      public function log_current_status()
      {
         foreach( $this->services as $service )
         {
            $service->log_current_status();
         }
      }

      private function sig_child( $signal )
      {
         $status = ServiceCheck::FAILURE;
         pcntl_signal( SIGCHLD, array( $this, "sig_child" ) );

         while ( ( $pid = pcntl_wait( $status, WNOHANG ) ) > 0 )
         {
            $service = $this->children[ $pid ];
            unset( $this->children[ $pid ] );

            if ( pcntl_wifexited( $status ) &&
                 pcntl_wexitstatus( $status ) == ServiceCheck::SUCCESS )
            {
               $status = ServiceCheck::SUCCESS;
            }

            $service->post_run( $status );
         }
      }

      private function sig_usr1( $signal )
      {
         pcntl_signal( SIGUSR1, array( $this, "sig_usr1" ) );
         $this->log_current_status();
      }
}

Это сложный класс. Конструктор считывает и выполняет синтаксический аналіз XML-файла, создавая все контролируемые службы и программы их протоколирования. Метод loop() — главный метод в данном классе. Он устанавливает требуемые обработчики сигналов и проверяет, можно ли создать новый дочерний процесс. Если можно немедленно запустить следующее событие (они отсортированы по временной метке next_attempt), то ответвляется новый процесс. Внутри дочернего процесса устанавливается таймер, предотвращающий более длительное выполнение теста, чем значение timeout, а затем выполняется тест, определенный методом run().

Существует также два обработчика сигналов. sig_child(), обработчик SIGCHLD собирает уничтоженные дочерние процессы и выполняет метод post_run() их службы. sig_usr1() обработчик SIGUSR1 просто вызывает методы log_current_status() всех зарегистрированных протоколирующих программ, которые можно использовать для получения текущего состояния системы в целом.

Сама по себе структура мониторинга, естественно, не делает ничего — необходима служба для проверки. Следующий класс проверяет получен ли от HTTP-сервера ответ с кодом 200.

class HTTP_ServiceCheck extends ServiceCheck
{
   public $url;
   public function __construct($params)
   {
      foreach($params as $k => $v) {
      $k = "$k";
      $this->$k = "$v";
      }
   }

   public function run()
   {
      if(is_resource(@fopen($this->url, "r"))) {
         return ServiceCheck::SUCCESS;
      }
      else {
         return ServiceCheck::FAILURE;
      }
   }
}

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

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

class EmailMe_ServiceLogger implements ServiceLogger {
   public function log_service_event(ServiceCheck $service)
   {
      if($service->current_status == ServiceCheck::FAILURE) {
         $message = "Проблема со службой {$service->description()}\r\n";
         mail('oncall@example.com', 'Service Event', $message);
         if($service->consecutive_failures() > 5) {
            mail('oncall_backup@example.com', 'Service Event', $message);
         }
      }
   }
  
   public function log_current_status(ServiceCheck $service)
   {
      return;
   }
}

Если служба отключена в течение пяти проверок, то процесс также отправляет сообщение на резервный адрес. В данном процессе не реализован выразительный метод log_current_status().

Ниже приведена реализация процесса ServiceLogger, который протоколирует все изменения состояния службы в журнал ошибок PHP.

class ErrorLog_ServiceLogger implements ServiceLogger {
   public function log_service_event(ServiceCheck $service)
   {
      if($service->current_status() !== $service->previous_status()) {
         if($service->current_status() === ServiceCheck::FAILURE) {
            $status = 'DOWN';
         }
         else {
            $status = 'UP';
         }
         error_log("Служба {$service->description()} изменила состояние на $status");
      }
   }
   
   public function log_current_status(ServiceCheck $service)
   {
      error_log("{$service->description()}: $status");
   }
}

Метод log_current_status() означает, что, если процессу отправляется сигнал SIGUSR1, полное текущее состояние записывается в журнал ошибок PHP.

Ядро принимает конфигурационный файл, подобный приведенному ниже.

<config>
    <loggers>
        <logger>
            <id>errorlog</id>
            <class>ErrorLog_ServiceLogger</class>
        </logger>
        <logger>
            <id>emailme</id>
            <class>EmailMe_ServiceLogger</class>
        </logger>
    </loggers>
    <services>
        <service>
            <class>HTTP_ServiceCheck</class>
            <params>
                <description>OmniTI HTTP Check</description>
                <url>http://www.omniti.com</url>
                <timeout>30</timeout>
                <frequency>900</frequency>
            </params>
            <loggers>
                <logger>errorlog</logger>
                <logger>emailme</logger>
            </loggers>
        </service>
        <service>
            <class>HTTP_ServiceCheck</class>
            <params>
                <description>Home Page HTTP Check</description>
                <url>http://www.schlossnagle.org/~george</url>
                <timeout>30</timeout>
                <frequency>3600</frequency>
            </params>
            <loggers>
                <logger>errorlog</logger>
            </loggers>
        </service>
    </services>
</config>

После передачи XML(файла конструктор ServiceCheckRunner создает протоколирующий объект для каждой указанной программы протоколирования. Затем он создает объект класса ServiceCheck для каждой указанной службы.

ПРИМЕЧАНИЕ

Примечание

Данный конструктор использует класс ReflectionClass для диагностики классов служби программ протоколирования перед тем, как пользователь попытается создать соответствующие им объекты. Это хорошо демонстрирует новый API-интерфейс Reflection в PHP 5. Reflection API предоставляет классы для диагностики почти любого внутреннего объекта (класса, метода или функции) в PHP.

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

ФлагОписание
[-f]Расположение конфигурационного файла ядра, который по умолчанию имеет имя monitor.xml
[-n]Размер пула дочернего процесса, который допускается ядром; по умолчанию равен 5
[-d]Флаг для отключения режима демона данного ядра; это полезно при написании отладочного кода для процесса ServiceLogger, который выводит информацию в stdout или stderr

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

require_once "Service.inc";
require_once "Console/Getopt.php";
$shortoptions = "n:f:d";
$default_opts = array('n' => 5, 'f' => 'monitor.xml');
$args = getOptions($default_opts, $shortoptions, null);
$fp = fopen("/tmp/.lockfile", "a");
if(!$fp || !flock($fp, LOCK_EX | LOCK_NB)) {
   fputs($stderr, "Невозможно заблокировать файл\n");
   exit;
}
if(!$args['d']) {
   if(pcntl_fork()) {
     exit;
   }
   posix_setsid();
   if(pcntl_fork()) {
      exit;
   }
}
fwrite($fp, getmypid());
fflush($fp);
$engine = new ServiceCheckRunner($args['f'], $args['n']);
$engine->loop();

Необходимо отметить, что в данном примере используется нестандартная функция getOptions(), определенная ранее в настоящей главе и позволяющая упростить работу по синтаксическому разбору ключей.

После написаниясоответствующего конфигурационного файла можно запускать сценарий следующим образом:

> ./monitor.php -f /etc/monitor.xml

Это переводит сценарий в режим демона, и мониторинг продолжается до тех пор, пока не будет выключена машина или сценарий не будет уничтожен.

Этот сценарий довольно сложен, но существуют возможности его усовершенствования; оставляем их в качестве практического упражнения читателям:

Дополнительная литература

По написанию shell(сценариев на PHP информационных ресурсов немного.

Perl имеет гораздо более давнюю традицию использования в качестве языка для административных задач. Книга Perl for Systems Administration Дэвида Н. Бланк(Эдельмана (David N. Blank(Edelman) написана хорошо, а сходность синтаксиса и функцій Perl и PHP облегчает перенесение имеющихся в ней Perl(примеров на PHP.

php|architect — электронное (а теперь также и печатное) периодическое издание (том 1, выпуск 12) представляет хорошую статью Марко Табини (Marco Tabini) по построению интерактивных терминальных приложений с помощью PHP и расширения ncurses. Журнал php|architect доступен по адресу http://www.phparch.com.

PHP-GTK — интересный проект, направленный на написание настольных GUI-приложений на PHP с помощью графического инструментария GTK. Информация по PHP-GTK доступна по адресу http://gtk.php.net.

Эффективной системой контроля ресурсов, которая реализована в виде открытого исходного кода, является система Nagios, доступная на сайте http://nagios.org. Nagios послужила прообразом описанному в данной главе сценарию, который показывает, как писать интегрированные тесты на PHP. Кроме того, реализация базового ядра на PHP упрощает необходимую модернизацию интерфейсной части. (Nagios написана на C и основывается на CGI, что затрудняет ее модернизацию.)


Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.
    Сообщений 0    Оценка 0        Оценить