搜尋
首頁後端開發php教程如何写php守护进程(Daemon)

如何写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 _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 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_count0){ 
  
        $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"; 
} 
?> 


陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
在Laravel中使用Flash會話數據在Laravel中使用Flash會話數據Mar 12, 2025 pm 05:08 PM

Laravel使用其直觀的閃存方法簡化了處理臨時會話數據。這非常適合在您的應用程序中顯示簡短的消息,警報或通知。 默認情況下,數據僅針對後續請求: $請求 -

php中的捲曲:如何在REST API中使用PHP捲曲擴展php中的捲曲:如何在REST API中使用PHP捲曲擴展Mar 14, 2025 am 11:42 AM

PHP客戶端URL(curl)擴展是開發人員的強大工具,可以與遠程服務器和REST API無縫交互。通過利用Libcurl(備受尊敬的多協議文件傳輸庫),PHP curl促進了有效的執行

PHP記錄:PHP日誌分析的最佳實踐PHP記錄:PHP日誌分析的最佳實踐Mar 10, 2025 pm 02:32 PM

PHP日誌記錄對於監視和調試Web應用程序以及捕獲關鍵事件,錯誤和運行時行為至關重要。它為系統性能提供了寶貴的見解,有助於識別問題並支持更快的故障排除

簡化的HTTP響應在Laravel測試中模擬了簡化的HTTP響應在Laravel測試中模擬了Mar 12, 2025 pm 05:09 PM

Laravel 提供简洁的 HTTP 响应模拟语法,简化了 HTTP 交互测试。这种方法显著减少了代码冗余,同时使您的测试模拟更直观。 基本实现提供了多种响应类型快捷方式: use Illuminate\Support\Facades\Http; Http::fake([ 'google.com' => 'Hello World', 'github.com' => ['foo' => 'bar'], 'forge.laravel.com' =>

在Codecanyon上的12個最佳PHP聊天腳本在Codecanyon上的12個最佳PHP聊天腳本Mar 13, 2025 pm 12:08 PM

您是否想為客戶最緊迫的問題提供實時的即時解決方案? 實時聊天使您可以與客戶進行實時對話,並立即解決他們的問題。它允許您為您的自定義提供更快的服務

解釋PHP中晚期靜態結合的概念。解釋PHP中晚期靜態結合的概念。Mar 21, 2025 pm 01:33 PM

文章討論了PHP 5.3中介紹的PHP中的晚期靜態結合(LSB),允許靜態方法的運行時間分辨率調用以更靈活的繼承。 LSB的實用應用和潛在的觸摸

自定義/擴展框架:如何添加自定義功能。自定義/擴展框架:如何添加自定義功能。Mar 28, 2025 pm 05:12 PM

本文討論了將自定義功能添加到框架上,專注於理解體系結構,識別擴展點以及集成和調試的最佳實踐。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器