如何写php守护进程(Daemon)

 更新时间:2016年1月2日 10:01  点击:2040

守护进程(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守护进程的相关介绍,希望对大家的学习有所帮助。

[!--infotagslink--]

相关文章

  • 源码分析系列之json_encode()如何转化一个对象

    这篇文章主要介绍了源码分析系列之json_encode()如何转化一个对象,对json_encode()感兴趣的同学,可以参考下...2021-04-22
  • php中去除文字内容中所有html代码

    PHP去除html、css样式、js格式的方法很多,但发现,它们基本都有一个弊端:空格往往清除不了 经过不断的研究,最终找到了一个理想的去除html包括空格css样式、js 的PHP函数。...2013-08-02
  • index.php怎么打开?如何打开index.php?

    index.php怎么打开?初学者可能不知道如何打开index.php,不会的同学可以参考一下本篇教程 打开编辑:右键->打开方式->经文本方式打开打开运行:首先你要有个支持运行PH...2017-07-06
  • PHP中func_get_args(),func_get_arg(),func_num_args()的区别

    复制代码 代码如下:<?php function jb51(){ print_r(func_get_args()); echo "<br>"; echo func_get_arg(1); echo "<br>"; echo func_num_args(); } jb51("www","j...2013-10-04
  • PHP编程 SSO详细介绍及简单实例

    这篇文章主要介绍了PHP编程 SSO详细介绍及简单实例的相关资料,这里介绍了三种模式跨子域单点登陆、完全跨单点域登陆、站群共享身份认证,需要的朋友可以参考下...2017-01-25
  • PHP实现创建以太坊钱包转账等功能

    这篇文章主要介绍了PHP实现创建以太坊钱包转账等功能,对以太坊感兴趣的同学,可以参考下...2021-04-20
  • php微信公众账号开发之五个坑(二)

    这篇文章主要为大家详细介绍了php微信公众账号开发之五个坑,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2016-10-02
  • ThinkPHP使用心得分享-ThinkPHP + Ajax 实现2级联动下拉菜单

    首先是数据库的设计。分类表叫cate.我做的是分类数据的二级联动,数据需要的字段有:id,name(中文名),pid(父id). 父id的设置: 若数据没有上一级,则父id为0,若有上级,则父id为上一级的id。数据库有内容后,就可以开始写代码,进...2014-05-31
  • PHP如何通过date() 函数格式化显示时间

    这篇文章主要介绍了PHP如何通过date() 函数格式化显示时间,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-11-13
  • PHP+jQuery+Ajax实现多图片上传效果

    今天我给大家分享的是在不刷新页面的前提下,使用PHP+jQuery+Ajax实现多图片上传的效果。用户只需要点击选择要上传的图片,然后图片自动上传到服务器上并展示在页面上。...2015-03-15
  • golang与php实现计算两个经纬度之间距离的方法

    这篇文章主要介绍了golang与php实现计算两个经纬度之间距离的方法,结合实例形式对比分析了Go语言与php进行经纬度计算的相关数学运算技巧,需要的朋友可以参考下...2016-07-29
  • PHP如何使用cURL实现Get和Post请求

    这篇文章主要介绍了PHP如何使用cURL实现Get和Post请求,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-07-11
  • PHP正则表达式过滤html标签属性(DEMO)

    这篇文章主要介绍了PHP正则表达式过滤html标签属性的相关内容,实用性非常,感兴趣的朋友参考下吧...2016-05-06
  • 谈谈PHP中相对路径的问题与绝对路径的使用

    经常看到有人踩在了PHP路径的坑上面了,感觉有必要来说说PHP中相对路径的一些坑,以及PHP中绝对路径的使用,下面一起来看看。 ...2016-08-24
  • php构造方法中析构方法在继承中的表现

    这篇文章主要为大家详细介绍了php构造方法中析构方法在继承中的表现,感兴趣的小伙伴们可以参考一下...2016-04-15
  • thinkPHP中多维数组的遍历方法

    这篇文章主要介绍了thinkPHP中多维数组的遍历方法,以简单实例形式分析了thinkPHP中foreach语句的使用技巧,需要的朋友可以参考下...2016-01-12
  • php有序列表或数组中删除指定的值的实现代码

    这篇文章主要介绍了php有序列表或数组中删除指定的值的实现代码,删除给定的值之后,得到一个新的有序列表,长度-1,下面是具体的实现方法...2021-08-22
  • php图片添加文字水印实现代码

    这篇文章主要为大家详细介绍了php图片添加文字水印实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2016-03-17
  • PHP简单实现生成txt文件到指定目录的方法

    这篇文章主要介绍了PHP简单实现生成txt文件到指定目录的方法,简单对比分析了PHP中fwrite及file_put_contents等函数的使用方法,需要的朋友可以参考下...2016-04-28
  • thinkphp自定义权限管理之名称判断方法

    下面小编就为大家带来一篇thinkphp自定义权限管理之名称判断方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2017-04-03