最近个人项目中需要后台运行任务,之前一直是用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 守护进程类的更多相关文章

  1. python中的daemon守护进程实现方法

    原文参考:http://blog.csdn.net/tao_627/article/details/49532021 守护进程是生存期长的一种进程.它们独立于控制终端并且周期性的执行某种任务或等待处理 ...

  2. RabbitMQ PHP操作类,守护进程及相关测试数据

    封装类如下: <?php /* * amqp协议操作类,可以访问rabbitMQ * 需先安装php_amqp扩展 */ class RabbitMQCommand{ public $confi ...

  3. 并发编程(二)--利用Process类开启进程、僵尸进程、孤儿进程、守护进程、互斥锁、队列与管道

    一.multiprocessing模块 1.multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模块threading的编程接口类似. 2.mu ...

  4. 并发编程(二)——利用Process类开启进程、僵尸进程、孤儿进程、守护进程、互斥锁、队列与管道

    Process类与开启进程.守护进程.互斥锁 一.multiprocessing模块 1.multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模 ...

  5. Linux Supervisor 守护进程基本配置

    supervisor:C/S架构的进程控制系统,可使用户在类UNIX系统中监控.管理进程.常用于管理与某个用户或项目相关的进程. 组成部分supervisord:服务守护进程supervisorctl ...

  6. Android 通过JNI实现守护进程,使得Service服务不被杀死

    来自: http://finalshares.com/read-7306 转载请注明出处: http://blog.csdn.net/yyh352091626/article/details/5054 ...

  7. python实现Linux启动守护进程

    python实现Linux启动守护进程 DaemonClass.py代码: #/usr/bin/env python # -*- coding: utf-8 -*- import sys import ...

  8. centos 下Supervisor 守护进程基本配置

    supervisor:C/S架构的进程控制系统,可使用户在类UNIX系统中监控.管理进程.常用于管理与某个用户或项目相关的进程. 组成部分supervisord:服务守护进程supervisorctl ...

  9. php写守护进程(Daemon)

    守护进程(Daemon)是运行在后台的一种特殊进程.它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件.守护进程是一种很有用的进程.php也可以实现守护进程的功能. 1.基本概念 进程 ...

随机推荐

  1. 201521123040《Java程序设计》第5周学习总结

    1. 本周学习总结 1.1 尝试使用思维导图总结有关多态与接口的知识点. 2. 书面作业 1.代码阅读:Child压缩包内源代码 1.1 com.parent包中Child.java文件能否编译通过? ...

  2. 201521123003《Java程序设计》第4周学习总结

    1. 本章学习总结 你对于本章知识的学习总结 1.1 尝试使用思维导图总结有关继承的知识点. 参考资料: 百度脑图 XMind 1.2 使用常规方法总结其他上课内容. (1)了解了类型转换(cast) ...

  3. Java第十三周总结

    1. 本周学习总结 以你喜欢的方式(思维导图.OneNote或其他)归纳总结多网络相关内容. 2. 书面作业 1. 网络基础 1.1 比较ping www.baidu.com与ping cec.jmu ...

  4. 201521123008 《Java程序设计》 第九周学习总结

    1. 本周学习总结 2. 书面作业 本次PTA作业题集异常 1.常用异常 题目5-1 1.1 截图你的提交结果(出现学号) 1.2 自己以前编写的代码中经常出现什么异常.需要捕获吗(为什么)?应如何避 ...

  5. html css 前端基础 学习方法及经验分享

    前言: 入园第一天,想分享一点儿前端基础html css 的学习方法和一些经验给热爱前端,但是不知道从哪儿开始,依旧有些迷茫的新手朋友们.当然,适合每个人的学习方式不同,以下所讲的仅供参考. 一.关于 ...

  6. Laravel 源码解读系列第四篇-Auth 机制

    前言 Laravel有一个神器: php artisan make:auth 能够快速的帮我们完成一套注册和登录的认证机制,但是这套机制具体的是怎么跑起来的呢?我们不妨来一起看看他的源码.不过在这篇文 ...

  7. [LeetCode] 349 Intersection of Two Arrays && 350 Intersection of Two Arrays II

    这两道题都是求两个数组之间的重复元素,因此把它们放在一起. 原题地址: 349 Intersection of Two Arrays :https://leetcode.com/problems/in ...

  8. GCD之after

    先介绍下C中的modf函数 函数名:modf 头文件:<math.h> 函数原型:double modf(double x, double *ipart) 函数用途:分解x,以得到x的整数 ...

  9. 原型模式和基于原型继承的js对象系统

    像同样基于原型编程的Io语言一样,javascript在原型继承方面,实现原理和Io非常类似,javascript也遵守这些原则 所有数据都是对象 要得到一个对象,不是通过实例化类,而是找到一个对象作 ...

  10. MMORPG战斗系统随笔(二)、浅谈场寻路Flow Field PathFinding算法

    转载请标明出处http://www.cnblogs.com/zblade/ 今天给大家带来一篇游戏中寻路算法的博客.去年,我加入一款RTS的游戏项目,负责开发其中的战斗系统,战斗系统的相关知识,属于游 ...