18

免責事項

この場合、ソケットサーバーにとってPHPが最良の選択ではなかった可能性があることを私はよく知っています。異なる言語/プラットフォームを提案することは控えてください-私を信じてください-私はそれをあらゆる方向から聞いてきました。

Unix環境で作業し、PHP 5.2.17を使用している場合、私の状況は次のとおりです。フラッシュクライアントと通信するソケットサーバーをPHPで構築しました。私の最初の悩みは、各着信接続が処理が完了するまで順次接続をブロックすることでした。PHPを利用してこれを解決pcntl_fork()しました。他のクライアントへのメッセージのブロードキャストを処理する多数の子プロセス(親にPIDを保存)を正常に生成できたため、親プロセスを「解放」して、次の接続の処理を続行できるようになりました。

現在の私の主な問題は、これらのデッド/ゾンビの子プロセスのコレクションを処理/処理し、それらを終了することです。pcntl_fork()に関連するPHPのマニュアルページを(何度も)読んだところ、親プロセスが子のクリーンアップを担当していることがわかりました。親プロセスは、子がを実行すると、子からSIGNALを受け取りexit(0)ます。シグナルハンドラーpcntl_signal()を設定する関数を使用して、そのシグナルを「キャッチ」することができます。

私のsignal_handlerは次のようになります:

declare(ticks = 1); 
function sig_handler($signo){ 
  global $forks; // this is an array that holds all the child PID's
  foreach($forks AS $key=>$childPid){
    echo "has my child {$childPid} gone away?".PHP_EOL;
    if (posix_kill($childPid, 9)){
      echo "Child {$childPid} has tragically died!".PHP_EOL;
      unset($forks[$key]);
    }
  }
}

私は確かに、削除する必要がある関連する正しい子PIDを含む両方のエコーを確認していますが、

posix_kill($childPid, 9)

私が同義であると理解してkill -9 $childPidいるのは、実際にはプロセスを削除していませんが、TRUEを返しています...

のマニュアルページposix_killから取得:

成功した場合はTRUEを返し、失敗した場合はFALSEを返します。


コマンドを使用して子プロセスを監視していpsます。システム上では次のように表示されます。

web5      5296  5234  0 14:51 ?        00:00:00 [php] <defunct>
web5      5321  5234  0 14:51 ?        00:00:00 [php] <defunct>
web5      5466  5234  0 14:52 ?        00:00:00 [php] <defunct>

ご覧のとおり、これらのプロセスはすべて、PIDが5234

私は私の理解に何かが欠けていますか?私はなんとかすべてを機能させることができたようです(そしてそれは機能します)が、システム上に無数のゾンビプロセスが残っています!

ゾンビの黙示録の私の計画は堅実です-しかし、ゾンビの子プロセスを殺さない
場合でも、一体何ができるでしょうか?sudo kill -9


10日後に更新

私はいくつかの追加の調査の後にこの質問に自分で答えました、あなたがまだ私のとりとめのないことに耐えることができるならば、意のままに進んでください。

4

4 に答える 4

21

私は最後に解決策があることを約束します:P

了解しました...10日後、この問題は解決したと思います。すでに長い投稿に追加したくなかったので、この回答に私が試したことのいくつかを含めます。

@symのアドバイスを参考にして、ドキュメントとドキュメントのコメントを詳しく読むと、pcntl_waitpid()説明には次のように記載されています。

pidによって要求された子が呼び出しの時点までにすでに終了している場合(いわゆる
「ゾンビ」プロセス)、関数はすぐに戻ります。子が使用するシステムリソースはすべて
解放されます...

だから私pcntl_signal()はこのようにハンドラーを設定します-

function sig_handler($signo){ 
    global $childProcesses;
    $pid = pcntl_waitpid(-1, $status, WNOHANG);
    echo "Sound the alarm! ";
    if ($pid != 0){
        if (posix_kill($pid, 9)){
            echo "Child {$pid} has tragically died!".PHP_EOL;
            unset($childProcesses[$pid]);
        }
    }
}
// These define the signal handling
// pcntl_signal(SIGTERM, "sig_handler");
// pcntl_signal(SIGHUP,  "sig_handler");
// pcntl_signal(SIGINT, "sig_handler");
pcntl_signal(SIGCHLD, "sig_handler");

完了するために、子プロセスをフォークするために使用している実際のコードを含めます-

function broadcastData($socketArray, $data){
        global $db,$childProcesses;
        $pid = pcntl_fork();
        if($pid == -1) {
                // Something went wrong (handle errors here)
                // Log error, email the admin, pull emergency stop, etc...
                echo "Could not fork()!!";
        } elseif($pid == 0) {
                // This part is only executed in the child
                foreach($socketArray AS $socket) {
                        // There's more happening here but the essence is this
                        socket_write($socket,$msg,strlen($msg));

                        // TODO : Consider additional forking here for each client. 
                }
                // This is where the signal is fired
                exit(0);
        }

        // If the child process did not exit above, then this code would be
        // executed by both parent and child. In my case, the child will 
        // never reach these commands. 
        $childProcesses[] = $pid;
        // The child process is now occupying the same database 
        // connection as its parent (in my case mysql). We have to
        // reinitialize the parent's DB connection in order to continue using it. 
        $db = dbEngine::factory(_dbEngine); 
}

ええ...それはコードに対する1:1のコメントの比率です:P

だからこれは素晴らしく見えて、私はのエコーを見ました:

アラームを鳴らしてください!子供12345は悲劇的に死にました!

ただし、ソケットサーバーループが次の反復を実行したとき、socket_select()関数は次のエラーをスローして失敗しました。

PHP警告:socket_select():選択できません[4]:システムコールが中断されました...

サーバーはハングし、ルート端末からの手動のkillコマンド以外の要求に応答しなくなります。


なぜこれが起こったのか、それをデバッグするためにその後何をしたのかについては説明しません...イライラする1週間だったとだけ言っておきましょう...

たくさんのコーヒー、目の痛み、そして10日後...

ロールロールしてください

TL&DR-解決策:

phpソケットのドキュメントの2007年のコメントと、stuporglueに関するこのチュートリアル(「適切な子育て」を検索)で言及されているように、関数に渡すことで、子プロセス()からのシグナルを単に「無視できます。SIGCHLDSIG_IGNpcntl_signal()

pcntl_signal(SIGCHLD, SIG_IGN);

そのリンクされたブログ投稿からの引用:

SIGCHLDを無視している場合、子プロセスは完了時に自動的に取得されます。

信じられないかもしれませんが、私はそのpcntl_signal()行を含め、他のすべてのハンドラーと子供を扱うものを削除しました、そしてそれはうまくいきました!<defunct>ぶらぶらしているプロセスはもうありませんでした!

私の場合、子プロセスがいつ死んだのか、それが誰であるのかを正確に知ることは本当に興味がありませんでした。彼らがぶらぶらしてサーバー全体をクラッシュさせなかっただけです:P

于 2012-04-11T22:35:14.507 に答える
4

免責事項について-PHPは、サーバーを作成するための他の多くの言語よりも優れている/劣っていません。実行できないことがいくつかあります(軽量プロセス、非同期I / O)が、これらは実際にはフォークサーバーには適用されません。OOコードを使用している場合は、循環参照チェックのガベージコレクターが有効になっていることを確認してください。

子プロセスが終了すると、親プロセスがクリーンアップするまでゾンビになります。あなたのコードは、シグナルを受信すると、すべての子供にKILLシグナルを送信するようです。プロセスエントリはクリーンアップされません。exitを呼び出さなかったプロセスを終了します。子プロセスを正しく取得するには、waitpidを呼び出す必要があります(pcntl_waitのマニュアルページにあるこの例も参照してください)。

于 2012-04-02T12:49:38.323 に答える
2

http://www.linuxsa.org.au/tips/zombies.html

ゾンビは死んだプロセスです。死者を殺すことはできません。すべてのプロセスは最終的には死に、死ぬとゾンビになります。彼らはほとんど資源を消費しません、それは彼らが死んでいるので予想されることです!ゾンビの理由は、ゾンビの親(プロセス)がゾンビの終了ステータスとリソース使用統計を取得できるようにするためです。親は、wait()システムコールの1つを使用して、ゾンビが不要になったことをオペレーティングシステムに通知します。

プロセスが停止すると、その子プロセスはすべて、初期化プロセスであるプロセス番号1の子になります。Initは、子供がゾンビのままにならないように、子供が死ぬのを「常に」待っています。

ゾンビプロセスがある場合は、それらのゾンビが親によって待機されていないことを意味します(ps -lで表示されるPPIDを確認してください)。3つの選択肢があります。親プロセスを修正します(待機させます)。親を殺します。またはそれと一緒に暮らす。ゾンビはpsの出力で1行を超えるだけなので、それと一緒に暮らすのはそれほど難しいことではないことを覚えておいてください。

于 2012-04-02T13:05:27.753 に答える
1

ゾンビプロセスの問題の解決策を探すのがどれほど難しいかはよくわかっています。数百または数千のそれらが存在する可能性についての私の懸念は、iノードが不足することでした(これが実際に問題になるかどうかはわかりませんが、正しいか間違っています)。

posix-setsid()にリンクされたpcntl_fork()マニュアルページだけがあれば、私たちの多くは、解決策が非常に単純であることに何年も前に気づいたでしょう。

于 2014-02-06T21:56:42.763 に答える