PHP 編寫守護進程

語言: CN / TW / HK

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

以上內容希望幫助到大家,更多PHP大廠PDF面試文檔,PHP進階架構視頻資料,PHP精彩好文免費獲取可以關注公眾號:PHP開源社區,或者訪問:

2021金三銀四大廠面試真題集錦,必看!

騰訊一面的Redis秒殺面試題你會麼?

四年精華PHP技術文章整理合集——PHP框架篇

四年精華PHP技術文合集——微服務架構篇

四年精華PHP技術文合集——分佈式架構篇

四年精華PHP技術文合集——高併發場景篇

四年精華PHP技術文章整理合集——數據庫篇