среда, 12 марта 2008 г.

PHP Daemon

А теперь немного извращений: демон на PHP.

ЧАСТЬ 1. ПИШЕМ ДЕМОНА.
Описание команд читать в мануалах. По-возможности, я даю на них ссылки.

declare(ticks=1);

$g_signals = array(
  'SIGTERM' => 0,
  'SIGHUP' => 0,
);

$g_ppid = null;
$g_pid = null;

dmn_sys_init(); // сердце демона!

$g_ppid = posix_getppid(); // pid родителя
$g_pid = posix_getpid(); // pid дочернего (текущего) процесса

// поехали!
while(1){
  // делаем что-то полезное.

  if($g_signals['SIGTERM'] == 1) // если получен сигнал завершения процесса, то не спеша, не паникуя тихонечко заканчиваем работу и выходим.
    break;

  if($sleep_time > 0)
    sleep(1); // спать одну секунду (на самом деле это идеологически не правильно, но для шаблона подойдет)
}

function dmn_sys_init(){
  global $g_pid;

  // 1. создаем дочерний процесс. при одном работающем это может быть не нужно, но если планируется запуск одного контрольного и нескольких рабочих процессов, то этот код необходим.
  if(-1 == ($g_pid = pcntl_fork()))
    exit("Could not fork.\n");

  // очень важный момент! Здесь определяем в каком мы процессе после разветвления.
  if($g_pid != 0){
    // 2. мы в родительском процессе
    // тут нам нужно отслеживать завершение дочерних процессов.
    // делается это банально просто: при создании процесса накручиваем счетчик дочерних процессов
    // при завершении дочернего процесса уменьшаем счетчик. Когда дочерних не осталось - выходим.
    // ждать завершения можно командой pcntl_wait.
    // написание этого кода я оставлю читателю в качестве домашного задания

    // для тех, кому лень думать есть приятные новости - дочерние процессы можно не ждать,
    // а делать в лучших традициях мира животных - выкидывать их из гнезда и тихонько помирать.
    exit("The end of parent process.\n");
  }

  // 3. мы в дочернем процессе!
  // отцепляем процесс от консоли
  if(-1 == posix_setsid())
    exit("Could not detach from terminal.\n");

  // устанавливаем хуки для обрабатываемых сигналов
  if(!pcntl_signal(SIGTERM, "dmn_sys_sig_handler"))
  exit("Could not setup SIGTERM handler.\n");

  if(!pcntl_signal(SIGHUP, "dmn_sys_sig_handler"))
  exit("Could not setup SIGHUP handler.\n");
}

// функция-хук обработки сигналов от операционной системы
function dmn_sys_sig_handler($signo)
{
  global $g_signals;

  switch ($signo){
  case SIGTERM:
    $g_signals['SIGTERM'] = 1;
    break;
  case SIGHUP:
    $g_signals['SIGHUP'] = 1;
    break;
  default:
    $g_signals['OTHER'] = $signo;
  }
}


ЧАСТЬ 2. ПИШЕМ RC СКРИПТЫ.
Здесь пример примитивнейшего RC скрипта для Gentoo.
RC скрипт и PHP файлы записать в /etc/init.d/my-php-daemon и /usr/lib/my-php-daemon соответственно.

#!/sbin/runscript

depend() {
  # выставляем зависимости
  need net
}

start() {
  # показываем юзеру что мы делаем
  ebegin "Starting my PHP daemon"

  # ведем лог
  echo --------------------- >> /var/log/my-php-daemon/rc.log
  date >> /var/log/my-php-daemon/rc.log

  # запускаем демоненка
  php-cgi -f /usr/lib/my-php-daemon/index.php >> /var/log/my-php-daemon/rc.log

  # конец
  eend $?
}

stop() {
  # показываем юзеру что мы делаем
  ebegin "Stopping my php daemon"
  # останавливаем демона так - ищем процесс, выполняющий файл /usr/lib/my-php-daemon/index.php и посылаем ему сигнал SIGTERM.
  # внимание! если таких процессов несколько, то все они получат сигналы SIGTERM.
  pkill -TERM -f /usr/lib/my-php-daemon/index.php

  # ведем лог
  date >> /var/log/my-php-daemon/rc.log
  echo --------------------- >> /var/log/my-php-daemon/rc.log

  # конец
  eend $?
}


Таким образом все варнинги и ошибки PHP будут сыпаться в /var/log/my-php-daemon/rc.log, но, к сожалению, без даты. Добавить дату - это будет читателю второе домашнее задание.

Конечно, мануал не претендует на полноту и безупречность, но это лучше, чем ничего. А еще лучше для простого изучения.

БОНУС-ТРЕК
Поскольку PHP бывает весьма не предсказуем, то для контроля работы демона можно написать небольшой cron скриптик:

$g_vs_gw = "http://gw1.viasms.ru?"
$g_vs_eid = "12345";
$g_vs_password = "secure_word";

$g_needed = array(
  'my-php-daemon' => array(
    'regexp'=>'/php-cgi.+my-php-daemon/',
    'reset_cmd'=>'/etc/init.d/my-php-daemon zap --nocolor',
    'start_cmd'=>'/etc/init.d/my-php-daemon start --nocolor'
  ),
);

// fill array with system fields
$needed = array();

foreach($g_needed as $key=>$val){
  $needed[$key] = $val;
  $needed[$key]['running'] = 0;
}

$exec = shell_exec("ps -C php-cgi -o pid= -o command=");
$pss = explode("\n", $exec);

foreach($needed as $key=>$val)
  if(!$val['running'])
    foreach($pss as $ps)
      if(preg_match($val['regexp'], $ps))
        $needed[$key]['running'] = 1;

foreach($needed as $key=>$val){
  if(!$val['running']){
    print date("Ymd-His") . "\n";

    print shell_exec($val['reset_cmd']);
    $cmdresult = shell_exec($val['start_cmd']);
    print $cmdresult;

    $msg = $key . ' service is down. ' . $cmdresult;
    $pass = md5(md5($g_vs_password) . $msg);

    $url = $g_vs_gw . '?' .
      'eid=' . urlencode($g_vs_eid) .
      '&pass=' . urlencode($pass) .
      '&msg=' . urlencode($msg);

    print $url . "\n";
    readfile($url);
    print "\n";
  }
}


Этот скрипт во время своего запуска проверяет все демоны, указанные в массиве $g_needed. Если один из них не работает, то скрипт сбрасывает его состояние (zap) и запускает снова. Ориентировано, опять таки, на дистрибутив Gentoo. Для подстройки для вашего дистрибутива подкрутите элементы массива reset_cmd и start_cmd.

О результате выполнения пишет лог и отправляет SMS сообщение с текстом о состоянии запуска.
В качестве SMS шлюза используется сайт viasms.ru. Для этого в переменных $g_vs_eid и $g_vs_password надо указать id и пароль события.

В результате, работы скрипта к вам придет SMS сообщение примерно следующего содержания:


my-php-daemon service is down. Starting my PHP daemon [ ok ].


И вы сможете дальше спокойно пить пиво :О)

1 комментарий:

Анонимный комментирует...
Этот комментарий был удален администратором блога.