分享PHP守护进程类

前端之家收集整理的这篇文章主要介绍了分享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 = '/tmp/daemon_apc_keys';

/**

  • User ID
  • @var int
    */
    public $userID = 65534; // nobody

/**

  • Group ID
  • @var integer
    */
    public $groupID = 65533; // nobody

/**

  • Terminate daemon when set identity failure ?
  • @var bool
  • @since 1.0.3
    */
    public $requireSetIdentity = false;

/**

  • Path to PID file
  • @var string
  • @since 1.0.1
    */
    public $pidFileLocation = '/tmp/daemon.pid';

/**

  • processLocation
  • 进程信息记录目录
  • @var string
    */
    public $processLocation = '';

/**

  • processHeartLocation
  • 进程心跳包文件
  • @var string
    */
    public $processHeartLocation = '';

/**

  • Home path
  • @var string
  • @since 1.0
    */
    public $homePath = '/';

/**

  • Current process ID
  • @var int
  • @since 1.0
    */
    protected $_pid = 0;

/**

  • Is this process a children
  • @var boolean
  • @since 1.0
    */
    protected $_isChildren = false;

/**

  • Is daemon running
  • @var boolean
  • @since 1.0
    */
    protected $_isRunning = false;

/**

  • Constructor
  • @return void
    */
    public function __construct() {
error_reporting(0);
set_time_limit(0);
ob_implicit_flush();

register_shutdown_function(array(&$this,'releaseDaemon'));

}

/**

  • 启动进程
  • @return bool
    */
    public function main() {
$this->_logMessage('Starting daemon');

if (!$this->_daemonize()) {
  $this->_logMessage('Could not start daemon',self::DLOG_ERROR);

  return false;
}

$this->_logMessage('Running...');

$this->_isRunning = true;

while ($this->_isRunning) {
  $this->_doTask();
}

return true;

}

/**

  • 停止进程
  • @return void
    */
    public function stop() {
$this->_logMessage('Stoping daemon');

$this->_isRunning = false;

}

/**

  • Do task
  • @return void
    */
    protected function _doTask() {
    // override this method
    }

/**

  • _logMessage
  • 记录日志
  • @param string 消息
  • @param integer 级别
  • @return void
    */
    protected function _logMessage($msg,$level = self::DLOG_NOTICE) {
    // override this method
    }

/**

  • Daemonize
  • Several rules or characteristics that most daemons possess:
    1. Check is daemon already running
    1. Fork child process
    1. Sets identity
    1. Make current process a session laeder
    1. Write process ID to file
    1. Change home path
    1. umask(0)
  • @access private
  • @since 1.0
  • @return void
    */
    private function _daemonize() {
ob_end_flush();

if ($this->_isDaemonRunning()) {
  // Deamon is already running. Exiting
  return false;
}

if (!$this->_fork()) {
  // Coudn't fork. Exiting.
  return false;
}

if (!$this->_setIdentity() && $this->requireSetIdentity) {
  // <a href="https://www.jb51.cc/tag/required/" target="_blank" class="keywords">required</a> identity set <a href="https://www.jb51.cc/tag/Failed/" target="_blank" class="keywords">Failed</a>. Exiting
  return false;
}

if (!posix_setsid()) {
  $this->_logMessage('Could not make the current process a session leader',self::DLOG_ERROR);

  return false;
}

if (!$fp = fopen($this->pidFileLocation,'w')) {
  $this->_logMessage('Could not write to PID file',self::DLOG_ERROR);
  return false;
} else {
  fputs($fp,$this->_pid);
  fclose($fp);
}

// 写入监控日志
$this->writeProcess();

chdir($this->homePath);
umask(0);

declare(ticks = 1);

pcntl_signal(SIGCHLD,array(&$this,'sigHandler'));
pcntl_signal(SIGTERM,'sigHandler'));
pcntl_signal(SIGUSR1,'sigHandler'));
pcntl_signal(SIGUSR2,'sigHandler'));

return true;

}

/**

  • Cheks is daemon already running
  • @return bool
    */
    private function _isDaemonRunning() {
$oldPid = file_get_contents($this->pidFileLocation);

if ($oldPid !== false && posix_kill(trim($oldPid),0))
{
  $this->_logMessage('Daemon already running with PID: '.$oldPid,(self::DLOG_TO_CONSOLE | self::DLOG_ERROR));

  return true;
}
else
{
  return false;
}

}

/**

  • Forks process
  • @return bool
    */
    private function _fork() {
$this->_logMessage('Forking...');

$pid = pcntl_fork();

if ($pid == -1) {
  // 出错
  $this->_logMessage('Could not fork',self::DLOG_ERROR);

  return false;
} elseif ($pid) {
  // 父进程
  $this->_logMessage('Killing parent');

  exit();
} else {
  // fork的子进程
  $this->_isChildren = true;
  $this->_pid = posix_getpid();

  return true;
}

}

/**

  • Sets identity of a daemon and returns result
  • @return bool
    */
    private function _setIdentity() {
if (!posix_setgid($this->groupID) || !posix_setuid($this->userID))
{
  $this->_logMessage('Could not set identity',self::DLOG_WARNING);

  return false;
}
else
{
  return true;
}

}

/**

  • Signals handler
  • @access public
  • @since 1.0
  • @return void
    */
    public function sigHandler($sigNo) {
switch ($sigNo)
{
  case SIGTERM:  // Shutdown
    $this->_logMessage('Shutdown signal');
    exit();
    break;

  case SIGCHLD:  // Halt
    $this->_logMessage('Halt signal');
    while (pcntl_waitpid(-1,$status,WNOHANG) > 0);
    break;
  case SIGUSR1:  // User-defined
    $this->_logMessage('User-defined signal 1');
    $this->_sigHandlerUser1();
    break;
  case SIGUSR2:  // User-defined
    $this->_logMessage('User-defined signal 2');
    $this->_sigHandlerUser2();
    break;
}

}

/**

  • Signals handler: USR1
  • 主要用于定时清理每个进程里被缓存的域名dns解析记录
  • @return void
    */
    protected function _sigHandlerUser1() {
    apc_clear_cache('user');
    }

/**

  • Signals handler: USR2
  • 用于写入心跳包文件
  • @return void
    */
    protected function _sigHandlerUser2() {
$this->_initProcessLocation();

file_put_contents($this->processHeartLocation,time());

return true;

}

/**

  • Releases daemon pid file
  • This method is called on exit (destructor like)
  • @return void
    */
    public function releaseDaemon() {
if ($this->_isChildren && is_file($this->pidFileLocation)) {
  $this->_logMessage('Releasing daemon');

  unlink($this->pidFileLocation);
}

}

/**

  • writeProcess
  • 将当前进程信息写入监控日志,另外的脚本会扫描监控日志的数据发送信号,如果没有响应则重启进程
  • @return void
    */
    public function writeProcess() {
// 初始化 proc
$this->_initProcessLocation();

$command = trim(implode(' ',$_SERVER['argv']));

// 指定进程的目录
$processDir = $this->processLocation . '/' . $this->_pid;
$processCmdFile = $processDir . '/cmd';
$processPwdFile = $processDir . '/pwd';

// 所有进程所在的目录
if (!is_dir($this->processLocation)) {
  mkdir($this->processLocation,0777);
  chmod($processDir,0777);
}

// <a href="https://www.jb51.cc/tag/chaxun/" target="_blank" class="keywords">查询</a>重复的进程记录
$pDirObject = dir($this->processLocation);
while ($pDirObject && (($pid = $pDirObject->read()) !== false)) {
  if ($pid == '.' || $pid == '..' || intval($pid) != $pid) {
    continue;
  }

  $pDir = $this->processLocation . '/' . $pid;
  $pCmdFile = $pDir . '/cmd';
  $pPwdFile = $pDir . '/pwd';
  $pHeartFile = $pDir . '/heart';

  // 根据cmd检查启动相同参数的进程
  if (is_file($pCmdFile) && trim(file_get_contents($pCmdFile)) == $command) {
    unlink($pCmdFile);
    unlink($pPwdFile);
    unlink($pHeartFile);

    // 删目录有缓存
    usleep(1000);

    rmdir($pDir);
  }
}

// 新进程目录
if (!is_dir($processDir)) {
  mkdir($processDir,0777);
}

// 写入命令参数
file_put_contents($processCmdFile,$command);
file_put_contents($processPwdFile,$_SERVER['PWD']);

// 写<a href="https://www.jb51.cc/tag/wenjian/" target="_blank" class="keywords">文件</a>有缓存
usleep(1000);

return true;

}

/**

  • _initProcessLocation
  • 初始化
  • @return void
    */
    protected function _initProcessLocation() {
$this->processLocation = ROOT_PATH . '/app/data/proc';
$this->processHeartLocation = $this->processLocation . '/' . $this->_pid . '/heart';

}
}

原文链接:https://www.f2er.com/php/20624.html

猜你在找的PHP相关文章