1. 程式人生 > >php 守護進程類

php 守護進程類

create fun out started osi 生存 restart net eol

  最近個人項目中需要後臺運行任務,之前一直是用nouhp & + 重定向輸出 來後臺跑任務,後來覺得不好維護原始數據,同時可能也沒有直接操作進程那麽穩吧(沒驗證)。廢話少說,來看分析。

首先,我們守護進程的主要目的是創建一個長生存期的進程,獨立於控制端去完成你設置的任務,與此同時可以加入事件監聽或者 狀態輪詢,構成一個完整的運行機制


  php中使用pcntl_fork()來 創建子進程,如下程序會在父進程和子進程中分別運行,區別是返回的pid不一樣:

$pid = pcntl_fork();
if ($pid == -1) { 創建失敗
    // 當 pid 為 -1 的時候表示創建子進程失敗,這時返回-1。
return false; } else if ($pid) {//這裏運行的是父進程,但是pid 指的是他的子進程pid } else { //pid 為0 的時候,說明運行的子進程 }

  以上你需要 想象著 在不同的通道裏去執行代碼,進程只是計算機操作系統初期提出來一個形象化的概念,包括後面更加粒度更細化的線程。

需要註意的是 ,你想要獨立出來完成任務,那麽就不再依賴原來的環境了,包括目錄,上下文環境,

   這個時候我需要 php提供了 一些 set方法,用來設置進程的上下文環境,如下:

  posix_setsid(); chdir("/")
  可參考http://php.net/manual/zh/function.posix-setsid.php


  接著 為了讓進程便於我們控制並穩定的運行,我利用了 linux系統裏面的信號,通過信號來處理 相應的操作,
對於php提供的安裝信號的操作函數,可以使用 pcntl_signal(),http://php.net/manual/zh/function.pcntl-signal.php
對於信號的參考可以查看一下文章:http://www.cnblogs.com/guixiaoming/p/7700132.html
使用的例子(強行停止):
posix_kill($pid, SIGKILL)


最後就是一些異常處理,以下是我的個人項目中使用的守護進程:
abstract class DaemonBase
{
    
const LOG_ECHO = 1; const LOG_FILE = 2; /** * @var $child_pid 子進程的pid */ public $child_pid; public $pid_file; public $log_file; //每個子類的日誌文件 public $switch = true; //開關 public $cnt; //重啟的計數 public $do_works = []; public $sig_handlers = []; /** * 配置 */ public static $config = [ ‘current_dir‘ => ‘‘, ‘log_file‘ => ‘/tmp/‘ . __CLASS__ . ‘.log‘, ‘pid‘ => ‘/tmp/‘ . ‘daemon‘ . ‘.pid‘, ‘limit_memory‘ => -1, //分配最大內存 ‘max_times‘ => 0, //重啟的最大次數 ]; /** * 構造函數,設置path.以及註冊shutdown */ public function __construct() { $this->pid_file = ‘/tmp/‘ . get_class($this) . ‘.pid‘; $this->log_file = ‘/tmp/‘ . get_class($this) . ‘.log‘; set_error_handler([$this, ‘errorHandler‘]); register_shutdown_function(array($this, ‘shutdown‘)); ini_set(‘memory_limit‘, self::$config[‘limit_memory‘]); ini_set(‘display_errors‘, ‘Off‘); clearstatcache(); } /** * 執行啟動程序 * @param string $command */ public function run($command = ‘start‘) { if(empty($command) || !in_array($command, [‘start‘, ‘stop‘, ‘restart‘, ‘status‘])){ $command = ‘help‘; } $this->$command(); } /** * 開始 */ public function start() { $this->log(‘Starting daemon...‘, self::LOG_ECHO | self::LOG_FILE); $this->daemonize(); echo ‘Daemon #‘ . $this->getChildPid() . ‘ 啟動成功‘ .PHP_EOL; declare(ticks = 1){ while($this->switch){ $this->autoRestart(); $this->todo(); try { $this->main(); }catch (Exception $e) { var_dump($e); $this->log($e->getMessage(), self::LOG_FILE); } } } } /** * 停止 */ public function stop() { if (!$pid = $this->getChildPid()) { $this->log(‘守護進程 GG‘, self::LOG_FILE); exit(); } posix_kill($pid, SIGKILL); } protected function stopAll() { if (!is_writeable($this->log_file)) { $this->log(‘Daemon (no pid file) not running‘, self::LOG_ECHO); return FALSE; } $pid = $this->getChildPid(); unlink($this->log_file); $this->log(‘Daemon #‘ . $pid . ‘ has stopped‘, self::LOG_ECHO | self::LOG_FILE); $this->switch = TRUE; } public function restart() { if (!$pid = $this->getChildPid()) { $this->log(‘守護進程 GG‘, self::LOG_FILE); exit(); } posix_kill($pid, SIGHUP); } public function status() { if($pid = $this->getChildPid()) { $msg = "pid: $pid is running"; }else { $msg = "進程GG"; } $this->log($msg, self::LOG_ECHO); } /** * 幫助命令 */ public function help() { echo ‘start | stop | status | restart‘; } /** * 檢測能否正常啟動 * @return bool */ protected function check() { if ($pid = $this->getChildPid()) { $this->log("Daemon #{$pid} has already started", self::LOG_ECHO); return FALSE; } $dir = dirname(self::$config[‘pid‘]); if (!is_writable($dir)) { $this->log("you do not have permission to write pid file @ {$dir}", self::LOG_ECHO); return FALSE; } if (!is_writable(self::$config[‘log_file‘]) || !is_writable(dirname(self::$config[‘log_file‘]))) { $this->log("you do not have permission to write log file: {log_file}", self::LOG_ECHO); return FALSE; } if (!defined(‘SIGHUP‘)) { // Check for pcntl $this->log(‘PHP is compiled without --enable-pcntl directive‘, self::LOG_ECHO | self::LOG_FILE); return FALSE; } if (‘cli‘ !== php_sapi_name()) { // Check for CLI $this->log(‘You can only create daemon from the command line (CLI-mode)‘, self::LOG_ECHO | self::LOG_FILE); return FALSE; } if (!function_exists(‘posix_getpid‘)) { // Check for POSIX $this->log(‘PHP is compiled without --enable-posix directive‘, self::LOG_ECHO | self::LOG_FILE); return FALSE; } return TRUE; } /** * 創建子進程,並做好信號處理工作 */ protected function daemonize() { //檢查狀態 $this->check(); //fork 子進程 $this->fork(); //信號處理 $sig_array = [ SIGTERM => [$this, ‘defaultSigHandler‘], SIGQUIT => [$this, ‘defaultSigHandler‘], SIGINT => [$this, ‘defaultSigHandler‘], SIGHUP => [$this, ‘defaultSigHandler‘], ]; foreach ($sig_array as $signo => $callback) { pcntl_signal($signo, $callback); } file_put_contents($this->pid_file, $this->child_pid); } /** * fork 子進程 * @return bool */ protected function fork() { $pid = pcntl_fork(); if($pid == -1) { //創建子進程失敗 return false; } if($pid) { // 父進程 exit(); } //子進程 $this->child_pid = posix_getpid(); //子進程id posix_setsid(); //使進程成為會話組長,讓進程擺脫原會話的控制;讓進程擺脫原進程組的控制; return true; } /** * 重啟 */ protected function autoRestart() { if((self::$config[‘max_times‘] && $this->cnt >= self::$config[‘max_time‘]) || (0 !== self::$config[‘limit_memory‘] && memory_get_usage(TRUE) >= self::$config[‘limit_memory‘])) { $this->doworks = [[$this, ‘restart‘]]; $this->cnt = 0; } $this->cnt++; } public function getChildPid(){ if(!file_exists($this->pid_file)){ return false; } $pid = (int)file_get_contents($this->pid_file); return file_exists("/proc/{$pid}") ? $pid : FALSE; //檢測是否確實存在此進程 } public function todo() { foreach ($this->do_works as $row) { (1 === count($row)) ? call_user_func($row[0]) : call_user_func_array($row[0], $row[1]); } } /** * 需要執行的邏輯體 * * @return mixed */ abstract public function main(); public function defaultSigHandler($signo) { switch ($signo) { case SIGTERM: case SIGQUIT: case SIGINT: $this->do_works = [[$this, ‘stop‘]]; break; case SIGHUP: $this->do_works = [[$this, ‘restart‘]]; break; default: break; } } /** * Regist signo handler * * @param int $sig * @param callback $action */ public function regSigHandler($sig, $action) { $this->sig_handlers[$sig] = $action; } public function errorHandler($error_code, $msg){ } /** * 守護進程日誌 * * @param string $msg * @param int $io, 1->just echo, 2->just write, 3->echo & write */ public function log($msg, $io = self::LOG_FILE) { $datetime = date(‘Y-m-d H:i:s‘); $msg = "[{$datetime}] {$msg}\n"; if ((self::LOG_ECHO & $io) && !$this->child_pid) { echo $msg, "\n"; } if (self::LOG_FILE & $io) { file_put_contents($this->log_file, $msg, FILE_APPEND | LOCK_EX); } } /** * 腳本跑完執行 */ public function shutdown() { if ($error = error_get_last()) { $this->log(implode(‘|‘, $error), self::LOG_FILE); } if (is_writeable(self::$config[‘pid‘]) && $this->child_pid) { unlink(self::$config[‘pid‘]); } }

php 守護進程類