首頁  >  文章  >  後端開發  >  PHP實作系統程式設計 編寫守護程式詳解

PHP實作系統程式設計 編寫守護程式詳解

不言
不言原創
2018-04-13 10:48:051274瀏覽

這篇文章跟大家分享的內容是PHP實作系統程式設計之編寫守護程式詳解,有著一定的參考價值,有需要的朋友可以參考一下

(一)程式組、會話、控制終端、控制進程等概念

進程組:每個進程都有一個所屬的進程組(process group),進程組有一個進程組長( process group leader),進程組ID即為這個進程組長的進程號,所以判斷一個進程是否為進程組組長,只需比較該進稱號是否和它的進程組id相等即可,PHP中可以用函數posix_getpgrp() 取得目前進程的進程組id,用posix_getpid() 取得目前進程的進程號。


<?php

function isGroupLeader() {
    return posix_getpgrp() == posix_getpid();
}

$pid = pcntl_fork();

if($pid == 0) {
    echo &#39;子进程:&#39; . PHP_EOL;
}
elseif($pid > 0) {
    sleep(2);
    echo &#39;父进程:&#39; . PHP_EOL;
}

echo "当前进程组gid:" . posix_getpgrp() . PHP_EOL;
echo "当前进程号pid:" . posix_getpid() . PHP_EOL;

if (isGroupLeader()) {
    echo &#39;Is a process group leader&#39; . PHP_EOL;
}
else {
    echo &#39;Is not a process group leader&#39; . PHP_EOL;
}

以上程式會輸出:



子进程:
当前进程组gid:15827
当前进程号pid:15828
Is not a process group leader
父进程:
当前进程组gid:15827
当前进程号pid:15827
Is a process group leader

會話:會話(session)是若干進程組的集合,會話中的一個進程組為會話組長(session leader),會話ID即為這個會話組長的進程組id,PHP中可以使用函數posix_getsid (int $pid)  來取得指定進程的會話id,也可以使用函數posix_setsid() 來建立一個新的會話,此時該進程成為新會話的會話組長,該函數呼叫成功傳回新建立的會話ID,或失敗出錯時返回-1,注意linux中呼叫 posix_setsid() 函數的進程不能是進程組長,否則會呼叫失敗,這是由於一個進程組中的進程不能同時跨多個會話


linux 中關於setsid的文件介紹:


setsid()  creates  a  new  session  if  the calling process is not a process group leader.  The calling process is the leader of the new session, the process group
       leader of the new process group, and has no controlling tty.  The process group ID and session ID of the calling process are set to the PID of the calling process.
       The calling process will be the only process in this new process group and in this new session.
<?php

function isGroupLeader() {
    return posix_getpgrp() == posix_getpid();
}

echo "当前会话id: " . posix_getsid(0) . PHP_EOL; //传0表示获取当前进程的会话id

if (isGroupLeader()) {
    echo "当前进程是进程组长\n";
}

$ret = posix_setsid();  //创建一个新会话
var_dump($ret);  //由于当前进程是进程组长,此处会返回-1, 表示调用失败

以上例程會輸出:



#
当前会话id: 13000
当前进程是进程组长
int(-1)

那該如何新建會話呢,我們注意到前面使用pcntl_fork() 創建了一個子進程後,這個子進程就不是進程組長,所以可以利用子進程來建立新會話。



<?php
function isGroupLeader()
{
     return posix_getpgrp() == posix_getpid();
}
echo "当前进程所属会话ID:" . posix_getsid(0) . PHP_EOL;

$pid = pcntl_fork();

if ($pid > 0) {
    exit(0); // 让父进程退出
}
elseif ($pid == 0) {
    if (isGroupLeader()) {
        echo "是进程组组长\n";
    } else {
        echo "不是进程组组长\n";
    }
    echo "进程组ID:" . posix_getpgrp() . PHP_EOL;  
    echo "进程号pid: " . posix_getpid() . PHP_EOL;

    $ret = posix_setsid();
    var_dump($ret);

    echo "当前进程所属会话ID:" . posix_getsid(0) . PHP_EOL;
}

以上例程會輸出:



当前进程所属会话ID:13000
[root@localhost php]# 不是进程组组长
进程组ID:15856
进程号pid: 15857
int(15857)
当前进程所属会话ID:15857

利用子進程成功創建了新的會話。

控制終端機控制進程:(終端機是所有輸入輸出設備的總稱,例如鍵盤,滑鼠,顯示器都是一個終端)一個會話可以有一個控制終端,一個控制終端被一個會話獨佔。會話剛創建的時候是沒有控制終端的,但會話組長可以申請打開一個終端,如果這個終端不是其他會話的控制終端,這時的終端將會成為會話的控制終端,會話組長叫做控制進程。


linux下判斷一個會話是否擁有控制終端,我們可以嘗試開啟一個特殊的檔案/dev/tty , 他指向了真實的控制終端,如果開啟成功說明擁有控制終端,反之則沒有控制終端。


<?php
function isGroupLeader()
{
     return posix_getpgrp() == posix_getpid();
}

$pid = pcntl_fork();

if ($pid > 0) {
        sleep(1);
        $fp = fopen("/dev/tty", "rb");
        if ($fp) {
            echo "父进程会话 " . posix_getsid(0) . " 拥有控制终端\n";
        } else {
            echo "父进程会话 " . posix_getsid(0) . " 不拥有控制终端\n";
        }

    exit(0); // 让父进程退出
}
elseif ($pid == 0) {
    if (isGroupLeader()) {
        echo "是进程组组长\n";
    } else {
        echo "不是进程组组长\n";
    }

    $ret = posix_setsid();
    var_dump($ret);

    $fp = fopen("/dev/tty", "rb");
    if ($fp) {
            echo "子进程会话 " . posix_getsid(0) . " 拥有控制终端\n";
    }   else {
            echo "子进程会话 " . posix_getsid(0) . " 不拥有控制终端\n";
    }
}

上述程式子程序新建了一個會話,然後父子程序都嘗試開啟檔案/dev/tty,例程輸出如下:



#
不是进程组组长
int(15906)
PHP Warning:  fopen(/dev/tty): failed to open stream: No such device or address in /root/php/setsid.php on line 30

Warning: fopen(/dev/tty): failed to open stream: No such device or address in /root/php/setsid.php on line 30
子进程会话 15906 不拥有控制终端
父进程会话 13000 拥有控制终端

產生SIGHUP訊號

#1、當一個會話失去控制終端時,核心會向該會話的控制進程發送一個SIGHUP 訊號,而通常會話的控制進程是shell進程,shell在收到一個SIGHUP 訊號時,會向由它建立的所有進程組(前台或後台進程組)也發送一個SIGHUP訊號,然後退出,進程收到一個SIGHUP訊號的預設處理方式就是退出進程,當然進程也可以自訂訊號處理或忽略它。

2、另外,當控制進程終止時,核心也會傳送SIGHUP訊號給終端的前台進程組的所有成員。


<?php
$callback = function($signo){
        $sigstr = &#39;unkown signal&#39;;
        switch($signo) {
        case SIGINT:
            $sigstr = &#39;SIGINT&#39;;
            break;
        case SIGHUP:
            $sigstr = &#39;SIGHUP&#39;;
            break;
        case SIGTSTP:
            $sigstr = &#39;SIGTSTP&#39;;
            break;
        }
       file_put_contents("daemon.txt", "catch signal $sigstr\n", FILE_APPEND);
};

pcntl_signal(SIGINT, $callback);
pcntl_signal(SIGHUP, $callback);
pcntl_signal(SIGTSTP, $callback);

while(1)
{
    sleep(100);
    pcntl_signal_dispatch();
}

使用 php sighup.php運行起該程序,然後直接關掉終端,重新登入shell,會發現程式仍在運行,daemon.txt 檔案中會記錄擷取到的SIGHUP訊號。


[root@localhost php]# cat daemon.txt 
catch signal SIGHUP
[root@localhost php]# ps aux | grep sighup.php 
root     18438  0.0  0.4 191600  8996 ?        S    16:48   0:00 php sighup.php
root     18443  0.0  0.0 103328   896 pts/0    S+   16:53   0:00 grep sighup.php

同時linux下提供了一個nohup指令,可以讓行程忽略所有的SIGHUP訊號,例如


[root@localhost php]# nohup php sighup.php 
nohup: 忽略输入并把输出追加到"nohup.out"


(二)標準輸入、標準輸出、標準錯誤輸出

#php中有三個預設開啟的檔案句柄STDIN,STDOUT, STDERR 分別對應上述三個檔案描述符,而由於標準輸入輸出是和終端相關的,對於守護程序來說並沒有什麼用,可以直接關閉,但是直接關閉可能會造成一個問題,請看下面這段程式碼


<?php
fclose(STDOUT);

$fp = fopen("stdout.log", "a");

echo "hello world\n";

執行上述程式碼時,螢幕不會輸出echo的訊息,而是寫入到開啟的檔案中了,這是由於關閉STDOUT檔案句柄後,釋放了對應的檔案描述符,而linux開啟檔案總是使用最小的可用檔案描述符,所以這個檔案描述符現在指向fopen開啟的檔案了,導致原本寫到標準輸出的資訊現在寫到了檔案裡。為了避免這種怪異的行為,我們在關閉這三個檔案句柄之後可以立即打開 l​​inux提供的黑洞檔案 /dev/null,例如:


<?php
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);
fopen(&#39;/dev/null&#39;, &#39;r&#39;);
fopen(&#39;/dev/null&#39;, &#39;w&#39;);
fopen(&#39;/dev/null&#39;, &#39;w&#39;);

$fp = fopen("stdout.log", "a");

echo "hello world\n";

上面这个例程关闭STDIN,STDOUT, STDERR立马打开 /dev/null 三次,这样echo的信息会直接写到黑洞中,避免了前面出现的怪异的问题。


(三)编写守护进程涉及的其他问题

编写守护进程还涉及工作目录、文件掩码、信号处理、热更新、安全的启动停止等等问题,这里先留给大家自己百度,后期有空再来补充。


(四)一个守护进程的示例

<?php

//由于进程组长无法创建会话,fork一个子进程并让父进程退出,以便可以创建新会话
switch(pcntl_fork()) {
    case -1:
            exit("fork error");
            break;
    case 0: 
            break;
    default:
            exit(0); //父进程退出
}

posix_setsid();  //创建新会话,脱离原来的控制终端

//再次fork并让父进程退出, 子进程不再是会话首进程,让其永远无法打开一个控制终端
switch(pcntl_fork()) {
    case -1:
        exit("fork error");
        break;
    case 0:
        break;
    default:
        exit(0); //父进程退出
}

//关闭标准输入输出
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);
fopen(&#39;/dev/null&#39;, &#39;r&#39;);
fopen(&#39;/dev/null&#39;, &#39;w&#39;);
fopen(&#39;/dev/null&#39;, &#39;w&#39;);

//切换工作目录
chdir(&#39;/&#39;);

//清除文件掩码
umask(0);

//由于内核不会再为进程产生SIGHUP信号,我们可以使用该信号来实现热重启
pcntl_signal(SIGHUP, function($signo){
    //重新加载配置文件,重新打开日志文件等等
});

for(;;)
{
     pcntl_signal_dispatch();  //处理信号回调
    //实现业务逻辑
}



to be continue!

相关推荐:

PHP实现系统编程之网络Socket及IO多路复用

PHP实现系统编程之本地套接字(Unix Domain Socket)

PHP实现系统编程之 多进程编程介绍及孤儿进程、僵尸进程




以上是PHP實作系統程式設計 編寫守護程式詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn