PHP 編寫守護程式

streetlamp發表於2019-01-17

PHP建立守護程式

  • 程式根據狀態可以分為三種程式,守護程式,殭屍程式,孤兒程式。今天我們著重來分析下守護程式。

    簡介

    守護程式(daemon)是一類在後臺執行的特殊程式,用於執行特定的系統任務。很多守護程式在系統引導的時候啟動,並且一直執行直到系統關閉。另一些只在需要的時候才啟動,完成任務後就自動結束。

    建立步驟

  1. 建立子程式,終止父程式
    由於守護程式是脫離控制終端的,因此首先建立子程式,終止父程式,使得程式在shell終端裡造成一個已經執行完畢的假象。之後所有的工作都在子程式中完成,而使用者在shell終端裡則可以執行其他的命令,從而使得程式以殭屍程式形式執行,在形式I上做到了與控制終端的脫離。

  2. 在子程式中建立新會話
    這個步驟是建立守護程式中最重要的一步,在這裡使用的是系統函式setsid。setsid函式用於建立一個新的會話,並擔任該會話組的組長。呼叫setsid的三個作用:讓程式擺脫原會話的控制、讓程式擺脫原程式組的控制和讓程式擺脫原控制終端的控制。
    在呼叫fork函式時,子程式全盤複製父程式的會話期(session,是一個或多個程式組的集合)、程式組、控制終端等,雖然父程式退出了,但原先的會話期、程式組、控制終端等並沒有改變,因此,那還不是真正意義上使兩者獨立開來。setsid函式能夠使程式完全獨立出來,從而脫離所有其他程式的控制。

  3. 改變工作目錄
    使用fork建立的子程式也繼承了父程式的當前工作目錄。由於在程式執行過程中,當前目錄所在的檔案系統不能解除安裝,因此,把當前工作目錄換成其他的路徑,如“/”或“/tmp”等。改變工作目錄的常見函式是chdir。

  4. 重設檔案建立掩碼
    檔案建立掩碼是指遮蔽掉檔案建立時的對應位。由於使用fork函式新建的子程式繼承了父程式的檔案建立掩碼,這就給該子程式使用檔案帶來了諸多的麻煩。因此,把檔案建立掩碼設定為0,可以大大增強該守護程式的靈活性。設定檔案建立掩碼的函式是umask,通常的使用方法為umask(0)。

  5. 關閉檔案描述符
    用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);

執行方式

  • 開啟守護程式:php demon.php start
  • 停止守護程式:php demon.php stop
  • 檢視守護程式的狀態:php demon.php status

謝謝觀看。
streetlamp敬上。
福利:咕嚕咕嚕影視網

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章