page contents

PHP 编写守护进程

以下内容希望帮助到大家!

attachments-2020-04-94zVRubS5e8ac75ddb5d9.jpg

PHP 创建守护进程

  • 进程根据状态可以分为三种进程,守护进程,僵尸进程,孤儿进程。今天我们着重来分析下守护进程

简介

守护进程 (daemon) 是一类在后台运行的特殊进程,用于执行特定的系统任务。很多守护进程在系统引导的时候启动,并且一直运行直到系统关闭。另一些只在需要的时候才启动,完成任务后就自动结束。


创建步骤

 

创建子进程,终止父进程

由于守护进程是脱离控制终端的,因此首先创建子进程,终止父进程,使得程序在 shell 终端里造成一个已经运行完毕的假象。之后所有的工作都在子进程中完成,而用户在 shell 终端里则可以执行其他的命令,从而使得程序以僵尸进程形式运行,在形式  上做到了与控制终端的脱离。


在子进程中创建新会话

这个步骤是创建守护进程中最重要的一步,在这里使用的是系统函数 setsid。setsid 函数用于创建一个新的会话,并担任该会话组的组长。调用 setsid 的三个作用:让进程摆脱原会话的控制、让进程摆脱原进程组的控制和让进程摆脱原控制终端的控制。

在调用 fork 函数时,子进程全盘拷贝父进程的会话期 (session,是一个或多个进程组的集合)、进程组、控制终端等,虽然父进程退出了,但原先的会话期、进程组、控制终端等并没有改变,因此,那还不是真正意义上使两者独立开来。setsid 函数能够使进程完全独立出来,从而脱离所有其他进程的控制。

 

改变工作目录

使用 fork 创建的子进程也继承了父进程的当前工作目录。由于在进程运行过程中,当前目录所在的文件系统不能卸载,因此,把当前工作目录换成其他的路径,如 “/” 或 “/tmp” 等。改变工作目录的常见函数是 chdir。

 

重设文件创建掩码

文件创建掩码是指屏蔽掉文件创建时的对应位。由于使用 fork 函数新建的子进程继承了父进程的文件创建掩码,这就给该子进程使用文件带来了诸多的麻烦。因此,把文件创建掩码设置为 0,可以大大增强该守护进程的灵活性。设置文件创建掩码的函数是 umask,通常的使用方法为 umask (0)。

 

关闭文件描述符

fork 新建的子进程会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程读或写,但它们一样消耗系统资源,可能导致所在的文件系统无法卸载。

 

直接上代码


注:运行环境是 linux 系统,并且要在 cli 模式下运行。
文件名:deamon.php
<?php
/**
 * User: streetlamp
 * Date: 2019/1/9
 * Time: 15:14
 */

class Deamon{
    protected $_pidFile;
    public function __construct(){
        $this->_pidFile = '/var/www/html/queue/public/pid.log';
        $this->_checkPcntl();
    }

    /**
     * 创建守护进程核心函数
     * @return string|void
     */
    private function _demonize(){
        if (php_sapi_name() != 'cli') {
            die('Should run in CLI');
        }
        //创建子进程
        $pid = pcntl_fork();
        if ($pid == -1) {
            return 'fork faile';
        } elseif ($pid) {
            //终止父进程
            exit('parent process');
        }
        //在子进程中创建新的会话
        if (posix_setsid() === -1) {
            die('Could not detach');
        }
        //改变工作目录
        chdir('/');
        //重设文件创建的掩码
        umask(0);
        $fp = fopen($this->_pidFile, 'w') or die("Can't create pid file");
        //把当前进程的id写入到文件中
        fwrite($fp, posix_getpid());
        fclose($fp);
        //关闭文件描述符
        fclose(STDIN);
        fclose(STDOUT);
        fclose(STDERR);
        //运行守护进程的逻辑
        $this->job();
        return;
    }

    /**
     * 守护进程的任务
     */
    private function job(){
        //TODO 你的守护经常需要执行的任务
        while (true) {
            file_put_contents('/var/www/html/queue/public/job.log', 'job' . PHP_EOL, FILE_APPEND);
            sleep(5);
        }
    }

    /**
     * 获取守护进程的id
     * @return int
     */
    private function _getPid(){
        //判断存放守护进程id的文件是否存在
        if (!file_exists($this->_pidFile)) {
            return 0;
        }
        $pid = intval(file_get_contents($this->_pidFile));
        if (posix_kill($pid, SIG_DFL)) {//判断该进程是否正常运行中
            return $pid;
        } else {
            unlink($this->_pidFile);
            return 0;
        }
    }

    /**
     * 判断pcntl拓展
     */
    private function _checkPcntl(){
        !function_exists('pcntl_signal') && die('Error:Need PHP Pcntl extension!');
    }

    private function _message($message){
        printf("%s  %d %d  %s" . PHP_EOL, date("Y-m-d H:i:s"), posix_getpid(), posix_getppid(), $message);
    }

    /**
     * 开启守护进程
     */
    private function start(){
        if ($this->_getPid() > 0) {
            $this->_message('Running');
        } else {
            $this->_demonize();
            $this->_message('Start');
        }
    }

    /**
     * 停止守护进程
     */
    private function stop(){
        $pid = $this->_getPid();
        if ($pid > 0) {
            //通过向进程id发送终止信号来停止进程
            posix_kill($pid, SIGTERM);
            unlink($this->_pidFile);
            echo 'Stoped' . PHP_EOL;
        } else {
            echo "Not Running" . PHP_EOL;
        }
    }

    private function status(){
        if ($this->_getPid() > 0) {
            $this->_message('Is Running');
        } else {
            echo 'Not Running' . PHP_EOL;
        }
    }

    public function run($argv){
        $param = is_array($argv) && count($argv) == 2 ? $argv[1] : null;
        switch ($param) {
            case 'start':
                $this->start();
                break;
            case 'stop':
                $this->stop();
                break;
            case 'status':
                $this->status();
                break;
            default:
                echo "Argv start|stop|status " . PHP_EOL;
                break;
        }
    }
}
$deamon = new \Deamon();
$deamon->run($argv);


运行方式

  1. 开启守护进程:php demon.php start
  2. 停止守护进程:php demon.php stop
  3. 查看守护进程的状态:php demon.php status


attachments-2020-04-xYHbGGTk5e8ac8b32eb92.jpg

  • 发表于 2020-04-06 14:14
  • 阅读 ( 532 )
  • 分类:PHP开发

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
Pack
Pack

1135 篇文章

作家榜 »

  1. 轩辕小不懂 2403 文章
  2. 小柒 1316 文章
  3. Pack 1135 文章
  4. Nen 576 文章
  5. 王昭君 209 文章
  6. 文双 71 文章
  7. 小威 64 文章
  8. Cara 36 文章