PHP高度なプログラミング例:デーモンプロセスの作成

6974 ワード

1.デーモンプロセスとは
デーモンプロセスは、端末から離れてバックグラウンドで実行されるプロセスです.デーモンプロセスが端末から離れるのは、プロセスが実行中の情報が任意の端末に表示され、プロセスが任意の端末によって生成された端末情報によって中断されないようにするためである.
例えばapache,nginx,mysqlはすべてデーモンプロセスです
2.開発デーモンプロセス
多くのプログラムはサービス形式で存在し、端末やUIインタラクションがなく、TCP/UDP Socket、UNIX Socket、fifoなどの他のプログラムとインタラクションする可能性があります.プログラムが起動するとバックグラウンドに入り、条件が満たされるまでタスクを処理します.
3.デーモン開発アプリケーションの使用時期
私の現在のニーズを例にとると、プログラムを実行し、ポートを傍受し、サービス側が開始したデータを継続的に受け入れ、データ分析処理を行い、結果をデータベースに書き込む必要があります.ZeroMQでデータ送受信をしています.
もし私がデーモンプロセス方式でこのプログラムを開発しなければ、プログラムが実行されると現在の端末の窓枠を占有し、現在の端末のキーボード入力の影響を受けて、プログラムが誤って終了する可能性があります.
4.プロセスのセキュリティ問題の保護
プログラムがスーパーユーザー以外で実行されることを望んでいます.これにより、プログラムに脆弱性が発生してハッカーによって制御されると、攻撃者は実行権限を継承するしかなく、スーパーユーザー権限を取得できません.
ポート競合などの問題が発生するため、同僚が2つ以上のプログラムを開くのではなく、プログラムが1つのインスタンスしか実行できないことを望んでいます.
5.デーモンプロセスの開発方法
例1.デーモンプロセスの例

logger = $logger;
 #}

 #protected $logger;
 protected static $dbh;
 public function __construct() {

 }
 public function run(){
  $dbhost = '192.168.2.1';  //       
  $dbport = 3306;
   $dbuser = 'www';  //       
 $dbpass = 'qwer123';    //      
  $dbname = 'example';  //     

  self::$dbh = new PDO("mysql:host=$dbhost;port=$dbport;dbname=$dbname", $dbuser, $dbpass, array(
   /* PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'', */
   PDO::MYSQL_ATTR_COMPRESS => true,
   PDO::ATTR_PERSISTENT => true
   )
  );

 }
 protected function getInstance(){
 return self::$dbh;
  }

}

/* the collectable class implements machinery for Pool::collect */
class Fee extends Stackable {
 public function __construct($msg) {
  $trades = explode(",", $msg);
  $this->data = $trades;
  print_r($trades);
 }

 public function run() {
  #$this->worker->logger->log("%s executing in Thread #%lu", __CLASS__, $this->worker->getThreadId() );

  try {
   $dbh = $this->worker->getInstance();
   
   $insert = "INSERT INTO fee(ticket, login, volume, `status`) VALUES(:ticket, :login, :volume,'N')";
   $sth = $dbh->prepare($insert);
   $sth->bindValue(':ticket', $this->data[0]);
   $sth->bindValue(':login', $this->data[1]);
   $sth->bindValue(':volume', $this->data[2]);
   $sth->execute();
   $sth = null;
   
   /* ...... */
   
   $update = "UPDATE fee SET `status` = 'Y' WHERE ticket = :ticket and `status` = 'N'";
   $sth = $dbh->prepare($update);
   $sth->bindValue(':ticket', $this->data[0]);
   $sth->execute();
   //echo $sth->queryString;
   //$dbh = null;
  }
  catch(PDOException $e) {
   $error = sprintf("%s,%s
", $mobile, $id ); file_put_contents("mobile_error.log", $error, FILE_APPEND); } } } class Example { /* config */ const LISTEN = "tcp://192.168.2.15:5555"; const MAXCONN = 100; const pidfile = __CLASS__; const uid = 80; const gid = 80; protected $pool = NULL; protected $zmq = NULL; public function __construct() { $this->pidfile = '/var/run/'.self::pidfile.'.pid'; } private function daemon(){ if (file_exists($this->pidfile)) { echo "The file $this->pidfile exists.
"; exit(); } $pid = pcntl_fork(); if ($pid == -1) { die('could not fork'); } else if ($pid) { // we are the parent //pcntl_wait($status); //Protect against Zombie children exit($pid); } else { // we are the child file_put_contents($this->pidfile, getmypid()); posix_setuid(self::uid); posix_setgid(self::gid); return(getmypid()); } } private function start(){ $pid = $this->daemon(); $this->pool = new Pool(self::MAXCONN, \ExampleWorker::class, []); $this->zmq = new ZMQSocket(new ZMQContext(), ZMQ::SOCKET_REP); $this->zmq->bind(self::LISTEN); /* Loop receiving and echoing back */ while ($message = $this->zmq->recv()) { //print_r($message); //if($trades){ $this->pool->submit(new Fee($message)); $this->zmq->send('TRUE'); //}else{ // $this->zmq->send('FALSE'); //} } $pool->shutdown(); } private function stop(){ if (file_exists($this->pidfile)) { $pid = file_get_contents($this->pidfile); posix_kill($pid, 9); unlink($this->pidfile); } } private function help($proc){ printf("%s start | stop | help
", $proc); } public function main($argv){ if(count($argv) < 2){ printf("please input help parameter
"); exit(); } if($argv[1] === 'stop'){ $this->stop(); }else if($argv[1] === 'start'){ $this->start(); }else{ $this->help($argv[0]); } } } $cgse = new Example(); $cgse->main($argv);

5.1. プログラム起動
以下はプログラム起動後にバックグラウンドに入るコードです
プロセスIDファイルで現在のプロセス状態を判断し、プロセスIDファイルが存在する場合はプログラムが実行中であることを示すコードfile_exists($this->pidfile)は実装されますが、その後のプロセスはkillによって手動でファイルを削除して実行する必要があります.

private function daemon(){
  if (file_exists($this->pidfile)) {
   echo "The file $this->pidfile exists.
"; exit(); } $pid = pcntl_fork(); if ($pid == -1) { die('could not fork'); } else if ($pid) { // we are the parent //pcntl_wait($status); //Protect against Zombie children exit($pid); } else { // we are the child file_put_contents($this->pidfile, getmypid()); posix_setuid(self::uid); posix_setgid(self::gid); return(getmypid()); } }

プログラムが起動すると、親プロセスがリリースされ、サブプロセスがバックグラウンドで実行され、rootから指定したユーザーにサブプロセス権限が切り替わり、pidがプロセスIDファイルに書き込まれます.
5.2. プログラム停止
プログラムが停止し、pidファイルを読み込んでposix_を呼び出すだけです.kill($pid, 9); 最後にファイルを削除します.

private function stop(){

  if (file_exists($this->pidfile)) {
   $pid = file_get_contents($this->pidfile);
   posix_kill($pid, 9); 
   unlink($this->pidfile);
  }
 }