7

私は、人々がコードをオンラインでコンパイルして実行できる Web サイトを作ろうとしています。そのため、ユーザーが指示を送信するためのインタラクティブな方法を見つける必要があります。

実は最初に思い浮かぶのはexec()またはsystem()ですが、ユーザーが sth を入力したい場合、この方法では機能しません。したがって、 を使用する必要がありますproc_open()

たとえば、次のコード

int main()
{
    int a;
    printf("please input a integer\n");
    scanf("%d", &a);
    printf("Hello World %d!\n", a);
    return 0;
}

使った時はこんなproc_open()感じ

$descriptorspec = array(      
0 => array( 'pipe' , 'r' ) ,  
    1 => array( 'pipe' , 'w' ) ,  
    2 => array( 'file' , 'errors' , 'w' ) 
);  
$run_string = "cd ".$addr_base."; ./a.out 2>&1";
$process = proc_open($run_string, $descriptorspec, $pipes);
if (is_resource($process)) {
    //echo fgets($pipes[1])."<br/>";
    fwrite($pipes[0], '12');
    fclose($pipes[0]);
    while (!feof($pipes[1]))
        echo fgets($pipes[1])."<br/>";
    fclose($pipes[1]);
    proc_close($process);
}

C コードを実行するときに、最初の STDOUT ストリームを取得し、数値を入力してから、2 番目の STDOUT ストリームを取得したいと考えています。しかし、コメント行のコメントを外すと、ページがブロックされます。

問題を解決する方法はありますか?すべてのデータがそこに置かれていないときにパイプから読み取るにはどうすればよいですか? または、この種の対話型プログラムを作成するためのより良い方法はありますか?

4

2 に答える 2

20

それはもっと問題ですCglibcを使用する必要がありますfflush(stdout)

なんで?a.outターミナルでの実行と PHP からの呼び出しの違いは何ですか?

回答: 端末で実行する場合a.out(tty の標準入力)、glibc はライン バッファー IO を使用します。しかし、別のプログラム (この場合は PHP) から実行し、stdin がパイプ (または tty ではないもの) である場合、glibc は内部 IO バッファリングを使用します。そのため、fgets()コメントを外すと最初のブロックがブロックされます。詳細については、この記事を確認してください。

朗報:stdbufコマンドを使用して、このバッファリングを制御できます。変更$run_string:

$run_string = "cd ".$addr_base.";stdbuf -o0 ./a.out 2>&1";

ここに実用的な例があります。コマンドfflush()を使用しているため、C コードが気にしない場合でも機能します。stdbuf

サブプロセスの開始

$cmd = 'stdbuf -o0 ./a.out 2>&1';

// what pipes should be used for STDIN, STDOUT and STDERR of the child
$descriptorspec = array (
    0 => array("pipe", "r"),
    1 => array("pipe", "w"),
    2 => array("pipe", "w")
 );

// open the child
$proc = proc_open (
    $cmd, $descriptorspec, $pipes, getcwd()
);

すべてのストリームを非ブロッキング モードに設定する

// set all streams to non blockin mode
stream_set_blocking($pipes[1], 0);
stream_set_blocking($pipes[2], 0);
stream_set_blocking(STDIN, 0);

// check if opening has succeed
if($proc === FALSE){
    throw new Exception('Cannot execute child process');
}

子 pid を取得します。後で必要になります

// get PID via get_status call
$status = proc_get_status($proc);
if($status === FALSE) {
    throw new Exception (sprintf(
        'Failed to obtain status information '
    ));
}
$pid = $status['pid'];

子が終了するまでポーリングする

// now, poll for childs termination
while(true) {
    // detect if the child has terminated - the php way
    $status = proc_get_status($proc);
    // check retval
    if($status === FALSE) {
        throw new Exception ("Failed to obtain status information for $pid");
    }
    if($status['running'] === FALSE) {
        $exitcode = $status['exitcode'];
        $pid = -1;
        echo "child exited with code: $exitcode\n";
        exit($exitcode);
    }

    // read from childs stdout and stderr
    // avoid *forever* blocking through using a time out (50000usec)
    foreach(array(1, 2) as $desc) {
        // check stdout for data
        $read = array($pipes[$desc]);
        $write = NULL;
        $except = NULL;
        $tv = 0;
        $utv = 50000;

        $n = stream_select($read, $write, $except, $tv, $utv);
        if($n > 0) {
            do {
                $data = fread($pipes[$desc], 8092);
                fwrite(STDOUT, $data);
            } while (strlen($data) > 0);
        }
    }


    $read = array(STDIN);
    $n = stream_select($read, $write, $except, $tv, $utv);
    if($n > 0) {
        $input = fread(STDIN, 8092);
        // inpput to program
        fwrite($pipes[0], $input);
    }
}
于 2013-05-03T04:14:20.813 に答える