Démon PHP avec du fork :)

Démon PHP avec du fork :) - PHP - Programmation

Marsh Posté le 16-08-2011 à 10:02:55    

Hello à tous :)
 
Je sais que peu de gens utilisent PHP pour faire ce genre de chose... Mais je tente quand même :)
 
Je suis en train de faire un système de démon multi processus en PHP. A Dire vrai j'en avais déjà fait un pour ma boite, mais vu que je désire en faire une version plus clean, et plus générique, c'est le moment de peut être revoir certains choix techniques.
 
Pour info je créé mes processus avec un bon vieux fork (et non un process_open).
Cependant, après avoir créé les processus, il faut pouvoir dialoguer avec :)
 
Dans la première version que j'ai faite, j'avais utilisé les queues de message System V pour remplir ce rôle, mais en même temps je le dit qu'il y a peut être d'autres possibilité moins délicates à implémenter, ou plus efficaces. Je sais que c'est aussi possible d'utiliser les pipes.
 
Des idées ?

Reply

Marsh Posté le 16-08-2011 à 10:02:55   

Reply

Marsh Posté le 16-08-2011 à 10:22:12    

Assez étonnant d'utiliser PHP pour faire ce genre de chose :/ Du C/C++ aurait été plus adapté je trouve...


---------------
Astres, outil de help-desk GPL : http://sourceforge.net/projects/astres, ICARE, gestion de conf : http://sourceforge.net/projects/icare, Outil Planeta Calandreta : https://framalibre.org/content/planeta-calandreta
Reply

Marsh Posté le 16-08-2011 à 10:26:58    

rufo a écrit :

Assez étonnant d'utiliser PHP pour faire ce genre de chose :/ Du C/C++ aurait été plus adapté je trouve...


Oui, c'est ce qu'on me dit souvent :) Néanmoins, le premier démon a été utilisé dans ma boite, qui avait tous ses applicatifs en PHP. Donc...
De plus ce qui va tourner derrière sera en PHP aussi donc :)
 
je ne maitrise pas le C et encore moins le C++... Mais je suis d'accord avec toi.

Reply

Marsh Posté le 16-08-2011 à 11:23:19    

Ben en fait, que ce soit du C ou du PHP, (bien que le langage soit différent)
tu peux faire communiquer les processus, après c'est le comment.
 
D'un côté j'ai les queues de messages, il y a aussi les pipes, et peut être encore d'autres solutions... J'ai aussi vu qu'on pouvait faire ça avec des sémaphores, mais ça n'a plus l'air vraiment "à la mode"...

Reply

Marsh Posté le 18-08-2011 à 11:23:50    

Je te conseille de faire un shell_exec si tu es sous unix.
Le principe est d'avoir un père et des fils, mais dutiliser le même fichier.php pour cela, que le père va relancer en ligne de commande en passant en argument une marque de sa paternité (très utile dans le cas de plusieurs pères, pour savoir à quel papa tel ou tel gosse appartient)
 
Voici, en speed, une partie du code que j'utilise pour un crawler de site en PHP :
 

Code :
  1. /**
  2.  *
  3.  */
  4.     public function crawl($options = null) {
  5.         if (!isset($options['--url']) || !$options['--url']) {
  6.   return false;
  7.  }
  8.         $time = microtime(true);
  9.  // Master-url initialization
  10.  // ---------------------------------------------------------------------
  11.  if (isset($options['--master-url'])) {
  12.   $master_url = $options['--master-url'];
  13.  } else {
  14.             $master_url = $options['--url'];
  15.  }
  16.  // Master-id initialization
  17.  // ---------------------------------------------------------------------
  18.  if (isset($options['--master-id'])) {
  19.   $master_id = $options['--master-id'];
  20.  } else {
  21.   $command_line = '';
  22.   if (isset($options) && $options) {
  23.                 $command_line = PHP_CLI.' '.WSM_CRAWLER_PATH.'crawler.php';
  24.    foreach ($options as $k => $v) {
  25.     $command_line .= ' '.$k.' ';
  26.     if (is_bool($v)) {
  27.      $command_line .= (int)$v;
  28.     } else if (is_numeric($v)) {
  29.      $command_line .= (int)$v;
  30.     } else {
  31.      $command_line .= '\''.$v.'\'';
  32.     }
  33.    }
  34.   }
  35.             $master_id = $this->getDatabase()->exec('INSERT INTO `crawl` (`url`, `command_line`, `start_date`) VALUES (?, ?, ?)', array($options['--url'], $command_line, date('c')), null, false);
  36.       $options['--id'] = $this->insertUrls(array($options['--url']), $master_id);
  37.  }
  38.         // Parse the url
  39.  // ---------------------------------------------------------------------
  40.  $parse_result = $this->parse($options);
  41.  if (isset($parse_result['error'])) {
  42.       file_put_contents('./curlerror.txt', $parse_result['error']['id'].' - '.$parse_result['error']['message'], FILE_APPEND | LOCK_EX);
  43.  }
  44.  // Retry (X times maximum) if a 500 error is catched
  45.  // ---------------------------------------------------------------------
  46.         $max_retries = (isset($options['--500-max-retries']) && $options['--500-max-retries']) ? $options['--500-max-retries'] : 3;
  47.  if ($parse_result['http_code'] == 500) {
  48.   $wait_between_retries = (isset($options['--500-wait-between-retries']) && $options['--500-wait-between-retries']) ? $options['--500-wait-between-retries'] : 10000;
  49.   for ($i = 0; $i < $max_retries; ++$i) {
  50.    usleep($wait_between_retries);
  51.                 $parse_result = $this->parse($options);
  52.    if ($parse_result['http_code'] != 500) {
  53.     break;
  54.    }
  55.   }
  56.  }
  57.  // Add new urls to the master crawler queue
  58.  // ---------------------------------------------------------------------
  59.  if (isset($parse_result['links'])) {
  60.             $white_list_filters = array();
  61.   if (isset($options['--white-list']) && $options['--white-list']) {
  62.    $in_white_list = false;
  63.    if (strpos($options['--white-list'], '|') !== false) {
  64.     $white_list_filters = explode('|', $options['--white-list']);
  65.    } else {
  66.                       $white_list_filters = array($options['--white-list']);
  67.    }
  68.   }
  69.             $black_list_filters = array();
  70.   if (isset($options['--black-list']) && $options['--black-list']) {
  71.    $in_black_list = false;
  72.    if (strpos($options['--black-list'], '|') !== false) {
  73.     $black_list_filters = explode('|', $options['--black-list']);
  74.    } else {
  75.                       $black_list_filters = array($options['--black-list']);
  76.    }
  77.   }
  78.   $urls = array();
  79.   foreach ($parse_result['links'] as $link) {
  80.    if (isset($link['href']) && $link['href']) {
  81.     $url = $link['href'];
  82.     // Auto-prepend domain to domain-less urls
  83.     // ---------------------------------------------------------
  84.                 $url = preg_replace('`^/`Usiu', $this->getDomain($master_url).'/', $url);
  85.     // Delete anchors
  86.     // ---------------------------------------------------------
  87.                 $url = preg_replace('`#[^/]`Usiu', '', $url);
  88.     // If url is on the same level than the master url
  89.                 // ---------------------------------------------------------
  90.                 if ($this->getDomain($master_url) == $this->getDomain($url)) {
  91.                         $in_white_list = true;
  92.                         $in_black_list = false;
  93.                         // If url satisfies at least one of the url filters
  94.                  // ---------------------------------------------------------
  95.                     if ($white_list_filters) {
  96.        $in_white_list = false;
  97.        foreach ($white_list_filters as $filter) {
  98.         if (preg_match(trim($filter), $url)) {
  99.          $in_white_list = true;
  100.          break;
  101.         }
  102.        }
  103.       }
  104.       // If url do not satisfies any of the url filters
  105.                   // ---------------------------------------------------------
  106.                   if ($black_list_filters) {
  107.        foreach ($black_list_filters as $filter) {
  108.         if (preg_match(trim($filter), $url)) {
  109.          $in_black_list = true;
  110.          break;
  111.         }
  112.        }
  113.       }
  114.      if ($in_white_list && !$in_black_list) {
  115.       // If there is no preexisting url in the web page
  116.             // -------------------------------------------------
  117.                   if (!isset($urls[$url])) {
  118.        // If there is no preexisting url in database
  119.                 // ---------------------------------------------
  120.                    $res = $this->getDatabase()->req('SELECT 1
  121.                                                              FROM `url` `U`
  122.                                                             WHERE `U`.`fk_crawl` = ?
  123.                                                               AND `U`.`url` = ?', array($master_id, $url), null, false);
  124.        if (!$res) {
  125.         $urls[$url] = $url;
  126.        }
  127.       }
  128.      }
  129.     } else {
  130.      //echo 'The following url was excluded : '.$url."\n".'because '.$this->getDomain($master_url).' != '.$this->getDomain($url).''."\n";
  131.     }
  132.    }
  133.   }
  134.      if ($urls) {
  135.    $this->insertUrls($urls, $master_id);
  136.   }
  137.  }
  138.         // Update the url information
  139.  // ---------------------------------------------------------------------
  140.  $this->updateUrl($parse_result, $options['--id']);
  141.  // Master's  management of crawling processes
  142.  // ---------------------------------------------------------------------
  143.  if (!isset($options['--master-id'])) {
  144.             $nb_processes = $master_id ? $this->getNbProcesses($master_id) : 0;
  145.   $max_processes = (isset($options['--max-processes']) && $options['--max-processes'] > 0) ? $options['--max-processes'] : 10;
  146.   $start = true;
  147.   $nb_parsed_urls = 0;
  148.   $wait_for_url_label = '[Crawler '.$master_id.'] Waiting for urls ... ';
  149.             $wait_for_url_label_length = mb_strlen($wait_for_url_label);
  150.   $wait_for_url_token = array('-', '/', '|', '\\');
  151.             $wait_for_url_token_count = count($wait_for_url_token);
  152.             $max_processes_label = '[Crawler '.$master_id.'] Waiting for free processes ... ';
  153.             $max_processes_label_length = mb_strlen($max_processes_label);
  154.   $max_processes_token = array('-', '/', '|', '\\');
  155.             $max_processes_token_count = count($max_processes_token);
  156.             $urls = $this->getUnparsedUrls($master_id, $max_processes);
  157.   while ($urls || $start) {
  158.    $start = false;
  159.    foreach ($urls as $url) {
  160.          ++$nb_parsed_urls;
  161.     // Do not exceed the --max-urls limit
  162.     // ---------------------------------------------------------
  163.     if (isset($options['--max-urls']) && $options['--max-urls'] > 0) {
  164.      if ($nb_parsed_urls >= $options['--max-urls']) {
  165.       echo '[Crawler '.$master_id.'] --max-urls '.$options['--max-urls'].' reached, stop crawl'."\n";
  166.       break(2);
  167.      }
  168.     }
  169.     // Do not exceed the --max-processes limit
  170.     // ---------------------------------------------------------
  171.     $w = 0;
  172.                  while ($nb_processes >= $max_processes) {
  173.      if (!$w) {
  174.       echo $max_processes_label;
  175.      } else {
  176.                         $w2_max = mb_strlen($max_processes_token[($w % $max_processes_token_count)]);
  177.       for ($w2 = 0; $w2 < $w2_max; ++$w2) {
  178.        echo chr(8);
  179.       }
  180.      }
  181.      echo $max_processes_token[(++$w % $max_processes_token_count)];
  182.      //echo '[Crawler '.$master_id.'] --max-processes '.$nb_processes.' reached, wait '.$usleep.' ms'."\n";
  183.      usleep(1000);
  184.                         $nb_processes = $this->getNbProcesses($master_id);
  185.     }
  186.     if ($w) {
  187.      $w2_max = $max_processes_label_length;
  188.      for ($w2 = 0; $w2 < $w2_max + 1; ++$w2) {
  189.       echo chr(8);
  190.      }
  191.     }
  192.     // Lock the url, to not parse it twice
  193.     // ---------------------------------------------------------
  194.     $this->getDatabase()->exec('UPDATE `url` SET `fk_parsing_status` = ? WHERE `id` = ?', array(2, $url['id']), null, false);
  195.     // Instanciate a crawling process in background
  196.     // ---------------------------------------------------------
  197.     $command_line = PHP_CLI.' '.WSM_CRAWLER_PATH.'crawler.php';
  198.     $command_line .= ' --id \''.$url['id'].'\'';
  199.     $command_line .= ' --url \''.$url['url'].'\'';
  200.     $command_line .= ' --master-id \''.$url['masterId'].'\'';
  201.     $command_line .= ' --master-url \''.$url['masterUrl'].'\'';
  202.     foreach ($options as $option_name => $option_value) {
  203.      if ($option_name !== '--id'    &&
  204.       $option_name !== '--url'   &&
  205.       $option_name !== '--master-id'  &&
  206.       $option_name !== '--master-url'  &&
  207.       $option_name !== '--max-processes' &&
  208.       $option_name !== '--max-urls'  )
  209.                      $command_line .= ' '.$option_name.' \''.$option_value.'\'';
  210.     }
  211.     // Comment this line can help to see error messages related to wrong paths
  212.     $command_line .= ' > /dev/null'.' 2>/dev/null'.' &';
  213.     //echo 'Executing '.$command_line."\n";
  214.     shell_exec($command_line);
  215.                 $nb_processes = $this->getNbProcesses($master_id);
  216.     echo '[Crawler '.$master_id.'] Process '.$url['url'].' ('.$nb_processes.' process'.(($nb_processes < 2) ? '' : 'es').')'."\n";
  217.    }
  218.    // As threads are running in background, the program could finish
  219.    // because no url has been parsed (and no new url has been added
  220.    // to the list) before the end of the launch of the X firsts threads.
  221.    // That's why it is required to wait for some new urls, or kill
  222.    // the wait loop if all urls have been crawled without finding
  223.    // no new url.
  224.    $w = 0;
  225.                 while (!($urls = $this->getUnparsedUrls($master_id, $max_processes))) {
  226.     if (!$w) {
  227.      echo $wait_for_url_label;
  228.     } else {
  229.                         $w2_max = mb_strlen($wait_for_url_token[($w % $wait_for_url_token_count)]);
  230.      for ($w2 = 0; $w2 < $w2_max; ++$w2) {
  231.       echo chr(8);
  232.      }
  233.     }
  234.     echo $wait_for_url_token[(++$w % $wait_for_url_token_count)];
  235.     usleep(1000);
  236.                     $rs = $this->getDatabase()->req(' SELECT 1
  237.              FROM `url` `U`
  238.                WHERE `U`.`fk_crawl` = ?
  239.               AND `U`.`fk_parsing_status` != ?', array($master_id, 3), null, false);
  240.     if (!$rs) {
  241.      break;
  242.     }
  243.    }
  244.    if ($w) {
  245.     $w2_max = $wait_for_url_label_length;
  246.     for ($w2 = 0; $w2 < $w2_max + 1; ++$w2) {
  247.      echo chr(8);
  248.     }
  249.    }
  250.   }
  251.             // Master waits for its crawling processes to finish
  252.   // -----------------------------------------------------------------
  253.   $previous_nb_processes = $nb_processes;
  254.   while (($nb_processes = $this->getNbProcesses($master_id))) {
  255.    usleep(2000);
  256.    if ($nb_processes != $previous_nb_processes) {
  257.                  echo '[Crawler '.$master_id.'] No new url detected, waiting after '.$nb_processes.' process'.(($nb_processes < 2) ? '' : 'es').' to finish'."\n";
  258.    }
  259.                 $previous_nb_processes = $nb_processes;
  260.   }
  261.             $this->getDatabase()->exec('UPDATE `crawl` SET `end_date` = ?', array(date('c')), null, false);
  262.             echo '[Crawler '.$master_id.'] Crawl finished : '.$nb_parsed_urls.' urls parsed in '.(microtime(true) - $time).' s'."\n";
  263.  }
  264. }


 
 
La partie qui t'intéresse tout particulièrement est :
 

Code :
  1. // Instanciate a crawling process in background
  2.     // ---------------------------------------------------------
  3.     $command_line = PHP_CLI.' '.WSM_CRAWLER_PATH.'crawler.php';
  4.     $command_line .= ' --id \''.$url['id'].'\'';
  5.     $command_line .= ' --url \''.$url['url'].'\'';
  6.     $command_line .= ' --master-id \''.$url['masterId'].'\'';
  7.     $command_line .= ' --master-url \''.$url['masterUrl'].'\'';
  8.     foreach ($options as $option_name => $option_value) {
  9.      if ($option_name !== '--id'    &&
  10.       $option_name !== '--url'   &&
  11.       $option_name !== '--master-id'  &&
  12.       $option_name !== '--master-url'  &&
  13.       $option_name !== '--max-processes' &&
  14.       $option_name !== '--max-urls'  )
  15.                      $command_line .= ' '.$option_name.' \''.$option_value.'\'';
  16.     }
  17.     // Comment this line can help to see error messages related to wrong paths
  18.     $command_line .= ' > /dev/null'.' 2>/dev/null'.' &';
  19.     //echo 'Executing '.$command_line."\n";
  20.     shell_exec($command_line);
  21.                 $nb_processes = $this->getNbProcesses($master_id);


Message édité par CyberDenix le 18-08-2011 à 11:28:25

---------------
Directeur Technique (CTO)
Reply

Marsh Posté le 20-08-2011 à 13:41:51    

Pour le getNbProcess, il suffit d'executer en shell_exec un
 

Code :
  1. ps aux | grep lenomdufichierphp | grep empreintedupere | grep -v grep | wc -l


 
Le wc -l te retourne le nombre de lignes, le grep -v grep te filtre la commande grep elle-même (qui parasite le résultat)
 
Fais un trim() sur le résultat et tu obtiens le nombre de fils qui sont actuellement en cours d'execution


Message édité par CyberDenix le 20-08-2011 à 13:42:38

---------------
Directeur Technique (CTO)
Reply

Marsh Posté le 20-08-2011 à 14:02:16    

Merci pour ce bout de code, cependant, ton démon diffère quelques peu du mien...
 
Pour commencer, mes processus ont une durée de vie non déterminée. Ils vivent à vitam éternam :)
 
Voilà pourquoi ils doivent pouvoir communiquer après avoir été créés.
Dans ton cas, tu leur donne le boulot à leur création, dans le mien, ils doivent pouvoir recevoir du boulot à n'importe quel moment de leur exécution.

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed