如何写php守护进程(Daemon)

守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程是一种很有用的进程。php也可以实现守护进程的功能。
一、基本概念
进程: 每个进程都有一个父进程,子进程退出,父进程能得到子进程退出的状态。
进程组每个进程都属于一个进程组,每个进程组都有一个进程组号,该号等于该进程组组长的PID
二、守护编程要点
1. 在后台运行     
         为避免挂起控制终端将Daemon放入后台执行。方法是在进程中调用fork使父进程终止,让Daemon在子进程中后台执行。 if($pid=pcntl_fork()) exit(0);//是父进程,结束父进程,子进程继续
2. 脱离控制终端,登录会话和进程组 
       有必要先介绍一下Linux中的进程与控制终端,登录会话和进程组之间的关系:进程属于一个进程组,进程组号(GID)就是进程组长的进程号(PID)。登录会话可以包含多个进程组。这些进程组共享一个控制终端。这个控制终端通常是创建进程的登录终  端。 控制终端,登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们,使之不受它们的影响。方法是在第1点的基础上,调用setsid()使进程成为会话组长: posix_setsid();
        说明:当进程是会话组长时setsid()调用失败。但第一点已经保证进程不是会话组长。setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。由于会话过程对控制终端的独占性,进程同时与控制终端脱离。
3. 禁止进程重新打开控制终端
        现在,进程已经成为无终端的会话组长。但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端: if($pid=pcntl_fork()) exit(0);//结束第一子进程,第二子进程继续(第二子进程不再是会话组长)
4. 关闭打开的文件描述符
        进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的错误。按如下方法关闭它们:
        fclose(STDIN),fclose(STDOUT),fclose(STDERR)关闭标准输入输出与错误显示。
5. 改变当前工作目录
        进程活动时,其工作目录所在的文件系统不能卸下。一般需要将工作目录改变到根目录。对于需要转储核心,写运行日志的进程将工作目录改变到特定目录如chdir("/")
6. 重设文件创建掩模
        进程从创建它的父进程那里继承了文件创建掩模。它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩模清除:umask(0);
7. 处理SIGCHLD信号
        处理SIGCHLD信号并不是必须的。但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影  响服务器进程的并发性能。在Linux下可以简单地将SIGCHLD信号的操作设为SIG_IGN。 signal(SIGCHLD,SIG_IGN);
        这样,内核在子进程结束时不会产生僵尸进程。这一点与BSD4不同,BSD4下必须显式等待子进程结束才能释放僵尸进程。关于信号的问题请参考Linux 信号说明列表
三、实例

<?php
* 后台脚本控制类
*/
class DaemonCommand{ 

  private $info_dir="/tmp";
  private $pid_file="";
  private $terminate=false; //是否中断
  private $workers_count=0;
  private $gc_enabled=null;
  private $workers_max=8; //最多运行8个进程 

  public function __construct($is_sington=false,$user='nobody',$output="/dev/null"){ 

      $this->is_sington=$is_sington; //是否单例运行,单例运行会在tmp目录下建立一个唯一的PID
      $this->user=$user;//设置运行的用户 默认情况下nobody
      $this->output=$output; //设置输出的地方
      $this->checkPcntl();
  }
  //检查环境是否支持pcntl支持
  public function checkPcntl(){
    if ( ! function_exists('pcntl_signal_dispatch')) {
      // PHP < 5.3 uses ticks to handle signals instead of pcntl_signal_dispatch
      // call sighandler only every 10 ticks
      declare(ticks = 10);
    } 

    // Make sure PHP has support for pcntl
    if ( ! function_exists('pcntl_signal')) {
      $message = 'PHP does not appear to be compiled with the PCNTL extension. This is neccesary for daemonization';
      $this->_log($message);
      throw new Exception($message);
    }
    //信号处理
    pcntl_signal(SIGTERM, array(__CLASS__, "signalHandler"),false);
    pcntl_signal(SIGINT, array(__CLASS__, "signalHandler"),false);
    pcntl_signal(SIGQUIT, array(__CLASS__, "signalHandler"),false); 

    // Enable PHP 5.3 garbage collection
    if (function_exists('gc_enable'))
    {
      gc_enable();
      $this->gc_enabled = gc_enabled();
    }
  } 

  // daemon化程序
  public function daemonize(){ 

    global $stdin, $stdout, $stderr;
    global $argv; 

    set_time_limit(0); 

    // 只允许在cli下面运行
    if (php_sapi_name() != "cli"){
      die("only run in command line mode\n");
    } 

    // 只能单例运行
    if ($this->is_sington==true){ 

      $this->pid_file = $this->info_dir . "/" .__CLASS__ . "_" . substr(basename($argv[0]), 0, -4) . ".pid";
      $this->checkPidfile();
    } 

    umask(0); //把文件掩码清0 

    if (pcntl_fork() != 0){ //是父进程,父进程退出
      exit();
    } 

    posix_setsid();//设置新会话组长,脱离终端 

    if (pcntl_fork() != 0){ //是第一子进程,结束第一子进程
      exit();
    } 

    chdir("/"); //改变工作目录 

    $this->setUser($this->user) or die("cannot change owner"); 

    //关闭打开的文件描述符
    fclose(STDIN);
    fclose(STDOUT);
    fclose(STDERR); 

    $stdin = fopen($this->output, 'r');
    $stdout = fopen($this->output, 'a');
    $stderr = fopen($this->output, 'a'); 

    if ($this->is_sington==true){
      $this->createPidfile();
    } 

  }
  //--检测pid是否已经存在
  public function checkPidfile(){ 

    if (!file_exists($this->pid_file)){
      return true;
    }
    $pid = file_get_contents($this->pid_file);
    $pid = intval($pid);
    if ($pid > 0 && posix_kill($pid, 0)){
      $this->_log("the daemon process is already started");
    }
    else {
      $this->_log("the daemon proces end abnormally, please check pidfile " . $this->pid_file);
    }
    exit(1); 

  }
  //----创建pid
  public function createPidfile(){ 

    if (!is_dir($this->info_dir)){
      mkdir($this->info_dir);
    }
    $fp = fopen($this->pid_file, 'w') or die("cannot create pid file");
    fwrite($fp, posix_getpid());
    fclose($fp);
    $this->_log("create pid file " . $this->pid_file);
  } 

  //设置运行的用户
  public function setUser($name){ 

    $result = false;
    if (empty($name)){
      return true;
    }
    $user = posix_getpwnam($name);
    if ($user) {
      $uid = $user['uid'];
      $gid = $user['gid'];
      $result = posix_setuid($uid);
      posix_setgid($gid);
    }
    return $result; 

  }
  //信号处理函数
  public function signalHandler($signo){ 

    switch($signo){ 

      //用户自定义信号
      case SIGUSR1: //busy
      if ($this->workers_count < $this->workers_max){
        $pid = pcntl_fork();
        if ($pid > 0){
          $this->workers_count ++;
        }
      }
      break;
      //子进程结束信号
      case SIGCHLD:
        while(($pid=pcntl_waitpid(-1, $status, WNOHANG)) > 0){
          $this->workers_count --;
        }
      break;
      //中断进程
      case SIGTERM:
      case SIGHUP:
      case SIGQUIT: 

        $this->terminate = true;
      break;
      default:
      return false;
    } 

  }
  /**
  *开始开启进程
  *$count 准备开启的进程数
  */
  public function start($count=1){ 

    $this->_log("daemon process is running now");
    pcntl_signal(SIGCHLD, array(__CLASS__, "signalHandler"),false); // if worker die, minus children num
    while (true) {
      if (function_exists('pcntl_signal_dispatch')){ 

        pcntl_signal_dispatch();
      } 

      if ($this->terminate){
        break;
      }
      $pid=-1;
      if($this->workers_count<$count){ 

        $pid=pcntl_fork();
      } 

      if($pid>0){ 

        $this->workers_count++; 

      }elseif($pid==0){ 

        // 这个符号表示恢复系统对信号的默认处理
        pcntl_signal(SIGTERM, SIG_DFL);
        pcntl_signal(SIGCHLD, SIG_DFL);
        if(!empty($this->jobs)){
          while($this->jobs['runtime']){
            if(empty($this->jobs['argv'])){
              call_user_func($this->jobs['function'],$this->jobs['argv']);
            }else{
              call_user_func($this->jobs['function']);
            }
            $this->jobs['runtime']--;
            sleep(2);
          }
          exit(); 

        }
        return; 

      }else{ 

        sleep(2);
      } 

    } 

    $this->mainQuit();
    exit(0); 

  } 

  //整个进程退出
  public function mainQuit(){ 

    if (file_exists($this->pid_file)){
      unlink($this->pid_file);
      $this->_log("delete pid file " . $this->pid_file);
    }
    $this->_log("daemon process exit now");
    posix_kill(0, SIGKILL);
    exit(0);
  } 

  // 添加工作实例,目前只支持单个job工作
  public function setJobs($jobs=array()){ 

    if(!isset($jobs['argv'])||empty($jobs['argv'])){ 

      $jobs['argv']=""; 

    }
    if(!isset($jobs['runtime'])||empty($jobs['runtime'])){ 

      $jobs['runtime']=1; 

    } 

    if(!isset($jobs['function'])||empty($jobs['function'])){ 

      $this->log("你必须添加运行的函数!");
    } 

    $this->jobs=$jobs; 

  }
  //日志处理
  private function _log($message){
    printf("%s\t%d\t%d\t%s\n", date("c"), posix_getpid(), posix_getppid(), $message);
  } 

} 

//调用方法1
$daemon=new DaemonCommand(true);
$daemon->daemonize();
$daemon->start(2);//开启2个子进程工作
work(); 

//调用方法2
$daemon=new DaemonCommand(true);
$daemon->daemonize();
$daemon->addJobs(array('function'=>'work','argv'=>'','runtime'=>1000));//function 要运行的函数,argv运行函数的参数,runtime运行的次数
$daemon->start(2);//开启2个子进程工作 

//具体功能的实现
function work(){
   echo "测试1";
}
?> 

以上就是关于php守护进程的相关介绍,希望对大家的学习有所帮助。

(0)

相关推荐

  • PHP扩展程序实现守护进程

    一般Server程序都是运行在系统后台,这与普通的交互式命令行程序有很大的区别.glibc里有一个函数daemon.调用此函数,就可使当前进程脱离终端变成一个守护进程,具体内容参见man daemon.PHP中暂时没有此函数,当然如果你有兴趣的话,可以写一个PHP的扩展函数来实现. PHP命令行程序实现守护进程化有2种方法: 一 .使用nohup 复制代码 代码如下: nohup php myprog.php > log.txt & 这里就实现了守护进程化. 单独执行 php myprog.

  • PHP实现多进程并行操作的详解(可做守护进程)

    如下所示: 复制代码 代码如下: /** * 入口函数 * 将此文件保存为 ProcessOpera.php * 在terminal中运行 /usr/local/php/bin/php ProcessOpera.php & * 查看进程 ps aux|grep php */ProcessOpera("runCode", array(), 8); /** * run Code */function runCode($opt = array()) {   //需要在守护进程中运行的

  • php守护进程 加linux命令nohup实现任务每秒执行一次

    Unix中 nohup 命令功能就是不挂断地运行命令,同时 nohup 把程序的所有输出到放到当前目录 nohup.out 文件中,如果文件不可写,则放到 <用户主目录>/nohup.out 文件中.那么有了这个命令以后我们php就写成shell 脚本使用循环来让我们脚本一直运行下去,不管我们终端窗口是否关闭都能够让我们php 脚本一直运行下去. 马上动手写个 PHP 小程序,功能为每30秒记录时间,写入到文件 复制代码 代码如下: # vi for_ever.php #! /usr/loca

  • PHP守护进程的两种常见实现方式详解

    本文实例讲述了PHP守护进程的两种常见实现方式.分享给大家供大家参考,具体如下: 第一种方式,借助 nohup 和 &  配合使用. 在命令后面加上 & 符号, 可以让启动的进程转到后台运行,而不占用控制台,控制台还可以再运行其他命令,这里我使用一个while死循环来做演示,代码如下 <?php while(true){ echo time().PHP_EOL; sleep(3); } 用 & 方式来启动该进程 [root@localhost php]# php deadlo

  • php脚本守护进程原理与实现方法详解

    本文实例讲述了php脚本守护进程原理与实现方法.分享给大家供大家参考,具体如下: 思路: 1. while 循环,若当前没有数据要操作可以休眠: 2. crontab 脚本每隔固定时间段执行该脚本,执行时先检测是否已在执行,若无 执行,有则 跳过. 3. nohup  后台执行 4. flock -xn  加锁 实例: 要执行代码:index.php <?php set_time_limit(0); //死循环 while(1) { $message = '1111111' . "\n&q

  • PHP程序级守护进程的实现与优化的使用概述

    首先需要解释的是什么是守护进程. 守护进程就是在后台一直运行的进程.比如我们启动的httpd,mysqld等进程都是常驻内存内运行的程序. 针对需求进行分析: 需求:有一个常驻队列messageQueue(假设在redis内存中),这个队列会有可能有请求不定期的往队列中增加元素.同时我们要求在队列中有元素的时候,按照队列顺序将元素pop出来,并进行处理(假设这个处理只是echo 'test'); 解决方法: 现在假设已经有了两个函数 function oPopMessageQueue(){ -}

  • PHP守护进程实例

    php也是可以直接进行守护进程的启动与终止的,相对于shell来说会简单很多,理解更方便,当然了php的守护进程要实现自动重启还是要依赖于shell的crontab日程表,每隔一段时间去执行一次脚本看脚本是否需要重启,如果需要则杀掉进程删除RunFile文件,重新启动并在RunFile文件中写入pid. 复制代码 代码如下: <?php       function start($file){     $path = dirname(__FILE__).'/';     $runfile = $

  • shell脚本作为保证PHP脚本不挂掉的守护进程实例分享

    前几天开始跑一份数据名单,名单需要提供用户名.是否有手机号.是否有邮箱,用户名单我轻易的获取到了,但是,用户名单有2000w之多,并且去检测用户是否有手机号.是否有邮箱必须得通过一个对外开放的安全接口一个一个用户去请求,然后分析返回值才能知道.下面是我处理的方案:1.将2000w名单保存到临时数据表2.用PHP程序每次从该表获取500个用户,检测完后生成SQL update原纪录3.为了防止PHP程序突然断掉,用shell脚本每隔1分钟检测,PHP挂掉了则重启我使用shell脚本作为守护进程的原

  • 分享PHP守护进程类

    用PHP实现的Daemon类.可以在服务器上实现队列或者脱离 crontab 的计划任务.  使用的时候,继承于这个类,并重写 _doTask 方法,通过 main 初始化执行. <?php class Daemon { const DLOG_TO_CONSOLE = 1; const DLOG_NOTICE = 2; const DLOG_WARNING = 4; const DLOG_ERROR = 8; const DLOG_CRITICAL = 16; const DAPC_PATH =

  • PHP将进程作为守护进程的方法

    本文实例讲述了PHP将进程作为守护进程的方法.分享给大家供大家参考.具体分析如下: php中posix_setsid()的用法 文档解释是"Make the current process a session leader" 参考文档:http://linux.die.net/man/2/setsid 意思就是在一个进程组之间(父进程和子进程)调用这个函数的进程会被选举为进程组的leader 所以让一个进程成为守护进程的方法就是: 1 fork出一个子进程 2 在子进程posix_se

  • PHP程序员玩转Linux系列 使用supervisor实现守护进程

    PHP程序员玩转Linux系列文章: 1.PHP程序员玩转Linux系列-怎么安装使用CentOS 2.PHP程序员玩转Linux系列-lnmp环境的搭建 3.PHP程序员玩转Linux系列-搭建FTP代码开发环境 4.PHP程序员玩转Linux系列-备份还原MySQL 5.PHP程序员玩转Linux系列-自动备份与SVN 6.PHP程序员玩转Linux系列-Linux和Windows安装nginx 7.PHP程序员玩转Linux系列-nginx初学者引导 8.PHP程序员玩转Linux系列-N

  • PHP高级编程实例:编写守护进程

    1.什么是守护进程 守护进程是脱离于终端并且在后台运行的进程.守护进程脱离于终端是为了避免进程在执行过程中的信息在任何终端上显示并且进程也不会被任何终端所产生的终端信息所打断. 例如 apache, nginx, mysql 都是守护进程 2.为什么开发守护进程 很多程序以服务形式存在,他没有终端或UI交互,它可能采用其他方式与其他程序交互,如TCP/UDP Socket, UNIX Socket, fifo.程序一旦启动便进入后台,直到满足条件他便开始处理任务. 3.何时采用守护进程开发应用程

随机推荐