ホームページ  >  記事  >  バックエンド開発  >  PHPでシステムプログラミングを実装するためのデーモンプロセスの書き方を詳しく解説

PHPでシステムプログラミングを実装するためのデーモンプロセスの書き方を詳しく解説

不言
不言オリジナル
2018-04-13 10:48:051274ブラウズ

この記事の内容は、PHP でのシステムプログラミングのためのデーモンの作成について詳しく説明したもので、必要な方は参考にしてください

(1)プロセスグループ、セッション、制御ターミナル、制御プロセスなど。概念

: 各プロセスには、プロセス グループ リーダーが存在します。したがって、プロセス グループ ID は、プロセス リーダーのプロセス番号です。がプロセス グループ リーダーである場合、プロセス名がそのプロセス グループ ID と等しいかどうかを比較するだけで済みます。PHP では、関数 posix_getpgrp() を使用して現在のプロセスのプロセス グループ ID を取得し、posix_getpid() を使用できます。現在のプロセス グループ ID を取得します。


<?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: セッションは複数のプロセス グループの集合であり、セッション内の 1 つのプロセス グループがセッション リーダー、つまりセッションです。 ID はセッション リーダーのプロセス グループ ID です。PHP では、関数 posix_getsid(int $pid) を使用して指定したプロセスのセッション ID を取得するか、関数 posix_setsid() を使用して新しいセッションを作成できます。この時点で、プロセスは新しいセッションのセッション リーダーになります。この関数呼び出しは、正常に作成されたセッション ID を返します。失敗した場合は -1 を返します。Linux の呼び出しに注意してください posix_setsid()。 関数のプロセスをプロセス グループ リーダーにすることはできません。そうでない場合、プロセス グループ内のプロセスは同時に複数のセッションにまたがることができないため、呼び出しは失敗します。

setsid に関する Linux ドキュメント:

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

子プロセスを使用して新しいセッションが正常に作成されました。


制御端末
制御プロセス: (端末とは、すべての入出力デバイスの総称であり、キーボード、マウス、モニターなどはすべて端末です) セッションには 1 つの制御端末と 1 つの制御端末を含めることができますセッション専用です。セッション作成当初は制御端末が存在しませんが、セッションリーダーは端末の開設を申請することができ、この端末が他のセッションの制御端末でない場合には、その時点の端末がセッションの制御端末となります。セッションリーダーは制御プロセスと呼ばれます。

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 シグナルを送信します。シェルは 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 を使用してプログラムを実行し、ターミナルを直接閉じて再度シェルにログインすると、プログラムがまだ実行中であり、キャプチャされた SIGHUP シグナルがデーモンに記録されていることがわかります。 .txt ファイル。

[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 コマンドを提供します。これにより、プロセスは、

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


などのすべての SIGHUP シグナルを無視できます (2)

標準入力、標準出力、標準エラー出力


php では、STDIN、STDOUT、STDERR という 3 つのファイル ハンドルがデフォルトで開かれており、それぞれ上記の 3 つのファイル記述子に対応しています。標準入力と標準出力は端末に関連しているため、これらは役に立ちません。ただし、直接閉じると問題が発生する可能性がありますので、次のコードを参照してください

<?php
fclose(STDOUT);

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

echo "hello world\n";
上記のコードを実行すると、画面にはエコー情報が出力されませんが、ファイルを開く これは、STDOUT ファイルを閉じるためです。ハンドルの後、対応するファイル記述子が解放され、Linux はファイルを開くために常に使用可能な最小のファイル記述子を使用するため、このファイル記述子は fopen によって開かれたファイルを指すようになります。最初に標準出力に書き込まれた情報が、内部のファイルに書き込まれるようになります。この奇妙な動作を回避するには、次のような 3 つのファイル ハンドルを閉じた後、Linux によって提供されるブラック ホール ファイル /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 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。