9

問題

proc_open()シェルコマンドを呼び出すために利用した関数を使用していました。STDIO のやり方が間違っていたようで、PHP やターゲット コマンドがロックされることがありました。これは元のコードです:

function execute($cmd, $stdin=null){
    $proc=proc_open($cmd,array(0=>array('pipe','r'),1=>array('pipe','w'),2=>array('pipe','w')),$pipes);
    fwrite($pipes[0],$stdin);                fclose($pipes[0]);
    $stdout=stream_get_contents($pipes[1]);  fclose($pipes[1]);
    $stderr=stream_get_contents($pipes[2]);  fclose($pipes[2]);
    return array( 'stdout'=>$stdout, 'stderr'=>$stderr, 'return'=>proc_close($proc) );
}

ほとんどの場合は機能しますが、それだけでは十分ではなく、常に機能するようにしたいと考えています。

この問題はstream_get_contents()、STDIO バッファが 4k のデータを超える場合にロックアップすることにあります。

テストケース

function out($data){
    file_put_contents('php://stdout',$data);
}
function err($data){
    file_put_contents('php://stderr',$data);
}
if(isset($argc)){
    // RUN CLI TESTCASE
    out(str_repeat('o',1030);
    err(str_repeat('e',1030);
    out(str_repeat('O',1030);
    err(str_repeat('E',1030);
    die(128); // to test return error code
}else{
    // RUN EXECUTION TEST CASE
    $res=execute('php -f '.escapeshellarg(__FILE__));
}

STDERR と STDOUT に、合わせて 4120 バイト (4k を超える) の文字列を 2 回出力します。これにより、PHP が両側でロックアップします。

解決

どうやら、stream_select()行く方法です。次のコードがあります。

function execute($cmd,$stdin=null,$timeout=20000){
    $proc=proc_open($cmd,array(0=>array('pipe','r'),1=>array('pipe','w'),2=>array('pipe','w')),$pipes);
    $write  = array($pipes[0]);
    $read   = array($pipes[1], $pipes[2]);
    $except = null;
    $stdout = '';
    $stderr = '';
    while($r = stream_select($read, $write, $except, null, $timeout)){
        foreach($read as $stream){

            // handle STDOUT
            if($stream===$pipes[1])
/*...*/         $stdout.=stream_get_contents($stream);

            // handle STDERR
            if($stream===$pipes[2])
/*...*/         $stderr.=stream_get_contents($stream);
        }

        // Handle STDIN (???)
        if(isset($write[0])) ;

// the following code is temporary
$n=isset($n) ? $n+1 : 0; if($n>10)break; // break while loop after 10 iterations

    }
}

パズルの唯一の残りのピースは、STDIN の処理です ( の行を参照(???))。 STDIN は、自分の関数を呼び出しているものによって提供される必要があることがわかりましたexecute()。しかし、STDIN をまったく使用したくない場合はどうすればよいでしょうか。上記のテストケースでは、入力を求めませんでしたが、STDIN に対して何かを行うことになっています。

とはいえ、上記のアプローチは依然として でフリーズstream_get_contents()ます。私は何をすべきか/次に何をしようとしているのかよくわかりません.

クレジット

解決策は、元の問題を発見しただけでなく、Jakob Truelsen によって提案されました。4kのヒントも彼のアイデアでした。これに先立って、関数が正常に動作する理由について困惑していました (すべてがバッファ サイズに依存していることを知りませんでした)。

4

4 に答える 4

5

さて、1年が経過したようで、このことはまだ保留中であることを忘れました!

ただし、この混乱をGithubにある素敵なPHPクラスにまとめました。

残っている主な問題は、STDERRを読み取ると、PHPスクリプトがブロックされるため、無効になっていることです。

明るい面としては、イベントといくつかの優れたコーディングのおかげで(私は願っています!)、実行中のプロセスと実際に対話することができます(したがって、クラス名、InterExec)。したがって、PHPでボットスタイルの動作を行うことができます。

于 2012-12-17T11:46:33.237 に答える
0
while($r = stream_select($read, $write, $except, null, $timeout)){

私が知る限り、これは $r を変更されたストリームの数に設定します。これは 0 になる可能性があり、ループは継続しなくなります。PHPマニュアルに記載されているように、私はこれを個人的に再コーディングします:

while(false !== ($r = stream_select($read, $write, $except, null, $timeout))){

プロセスがインタラクティブでない場合、STDIN に関する限り、STDIN は必要ないかもしれません。実行しているプロセスは何ですか?

于 2011-05-18T06:43:40.600 に答える