分享PHP守護(hù)進(jìn)程類
用PHP實(shí)現(xiàn)的Daemon類??梢栽诜?wù)器上實(shí)現(xiàn)隊(duì)列或者脫離 crontab 的計(jì)劃任務(wù)。
使用的時(shí)候,繼承于這個(gè)類,并重寫 _doTask 方法,通過 main 初始化執(zhí)行。
<?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
* 進(jìn)程信息記錄目錄
*
* @var string
*/
public $processLocation = '';
/**
* processHeartLocation
* 進(jìn)程心跳包文件
*
* @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'));
}
/**
* 啟動(dòng)進(jìn)程
*
* @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;
}
/**
* 停止進(jìn)程
*
* @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
* 2) Fork child process
* 3) Sets identity
* 4) Make current process a session laeder
* 5) Write process ID to file
* 6) Change home path
* 7) 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) {
// Required identity set failed. 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);
}
// 寫入監(jiān)控日志
$this->writeProcess();
chdir($this->homePath);
umask(0);
declare(ticks = 1);
pcntl_signal(SIGCHLD, array(&$this, 'sigHandler'));
pcntl_signal(SIGTERM, array(&$this, 'sigHandler'));
pcntl_signal(SIGUSR1, array(&$this, 'sigHandler'));
pcntl_signal(SIGUSR2, array(&$this, '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) {
// 出錯(cuò)
$this->_logMessage('Could not fork', self::DLOG_ERROR);
return false;
} elseif ($pid) {
// 父進(jìn)程
$this->_logMessage('Killing parent');
exit();
} else {
// fork的子進(jìn)程
$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
* 主要用于定時(shí)清理每個(gè)進(jìn)程里被緩存的域名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
* 將當(dāng)前進(jìn)程信息寫入監(jiān)控日志,另外的腳本會(huì)掃描監(jiān)控日志的數(shù)據(jù)發(fā)送信號,如果沒有響應(yīng)則重啟進(jìn)程
*
* @return void
*/
public function writeProcess() {
// 初始化 proc
$this->_initProcessLocation();
$command = trim(implode(' ', $_SERVER['argv']));
// 指定進(jìn)程的目錄
$processDir = $this->processLocation . '/' . $this->_pid;
$processCmdFile = $processDir . '/cmd';
$processPwdFile = $processDir . '/pwd';
// 所有進(jìn)程所在的目錄
if (!is_dir($this->processLocation)) {
mkdir($this->processLocation, 0777);
chmod($processDir, 0777);
}
// 查詢重復(fù)的進(jìn)程記錄
$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';
// 根據(jù)cmd檢查啟動(dòng)相同參數(shù)的進(jìn)程
if (is_file($pCmdFile) && trim(file_get_contents($pCmdFile)) == $command) {
unlink($pCmdFile);
unlink($pPwdFile);
unlink($pHeartFile);
// 刪目錄有緩存
usleep(1000);
rmdir($pDir);
}
}
// 新進(jìn)程目錄
if (!is_dir($processDir)) {
mkdir($processDir, 0777);
chmod($processDir, 0777);
}
// 寫入命令參數(shù)
file_put_contents($processCmdFile, $command);
file_put_contents($processPwdFile, $_SERVER['PWD']);
// 寫文件有緩存
usleep(1000);
return true;
}
/**
* _initProcessLocation
* 初始化
*
* @return void
*/
protected function _initProcessLocation() {
$this->processLocation = ROOT_PATH . '/app/data/proc';
$this->processHeartLocation = $this->processLocation . '/' . $this->_pid . '/heart';
}
}
- PHP實(shí)現(xiàn)守護(hù)進(jìn)程的示例代碼
- PHP程序守護(hù)進(jìn)程化實(shí)現(xiàn)方法詳解
- php實(shí)現(xiàn)簡單的守護(hù)進(jìn)程創(chuàng)建、開啟與關(guān)閉操作
- PHP守護(hù)進(jìn)程的兩種常見實(shí)現(xiàn)方式詳解
- php腳本守護(hù)進(jìn)程原理與實(shí)現(xiàn)方法詳解
- 如何寫php守護(hù)進(jìn)程(Daemon)
- PHP擴(kuò)展程序?qū)崿F(xiàn)守護(hù)進(jìn)程
- PHP守護(hù)進(jìn)程實(shí)例
- php萬字碼出完美守護(hù)進(jìn)程詳解
相關(guān)文章
php實(shí)現(xiàn)的驗(yàn)證碼文件類實(shí)例
這篇文章主要介紹了php實(shí)現(xiàn)的驗(yàn)證碼文件類,實(shí)例分析了php生成驗(yàn)證碼文件的技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-06-06
解決FastCGI 進(jìn)程超過了配置的活動(dòng)超時(shí)時(shí)限的問題
本篇文章是對解決FastCGI 進(jìn)程超過了配置的活動(dòng)超時(shí)時(shí)限的方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-07-07
PHP實(shí)現(xiàn)的簡單在線計(jì)算器功能示例
這篇文章主要介紹了PHP實(shí)現(xiàn)的簡單在線計(jì)算器功能,涉及php數(shù)值運(yùn)算與表單操作相關(guān)技巧,需要的朋友可以參考下2017-08-08
實(shí)現(xiàn)在同一方法中獲取當(dāng)前方法中新賦值的session值解決方法
這篇文章主要介紹了在同一方法中獲取當(dāng)前方法中新賦值的session值解決方法,需要的朋友可以參考下2014-06-06
PHP實(shí)現(xiàn)的MD5結(jié)合RSA簽名算法實(shí)例
這篇文章主要介紹了PHP實(shí)現(xiàn)的MD5結(jié)合RSA簽名算法,結(jié)合實(shí)例形式分析了php使用md5結(jié)合RSA實(shí)現(xiàn)的簽名算法相關(guān)操作技巧,并附帶了RSA公鑰與私鑰的相關(guān)說明,需要的朋友可以參考下2017-10-10
詳解PHP版本兼容之openssl調(diào)用參數(shù)
這篇文章給大家分享了PHP版本兼容之openssl調(diào)用參數(shù)的詳細(xì)說明,有興趣的朋友參考學(xué)習(xí)下。2018-07-07
基于php實(shí)現(xiàn)長連接的方法與注意事項(xiàng)的問題
本篇文章是對在php中實(shí)現(xiàn)長連接的方法與注意事項(xiàng)的問題進(jìn)行了詳細(xì)的分析介紹。需要的朋友參考下2013-05-05

