2013-05-03 10 views
7

Ich versuche, eine Website, wo Menschen ihren Code online kompilieren und ausführen können, so müssen wir eine interaktive Möglichkeit für Benutzer zum Senden von Anweisungen finden.Lesen von STDIN-Rohr bei der Verwendung von proc_open

Eigentlich, was zuerst in den Sinn kommt ist exec() oder system(), aber wenn Benutzer etw eingeben möchten, wird dieser Weg nicht funktionieren. Also müssen wir proc_open() verwenden.

Zum Beispiel den folgenden Code

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

Als ich proc_open() verwendet, wie diese

$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); 
} 

Wenn die C-Code ausgeführt wird, ich das erste STDOUT Stream erhalten möchten, und geben Sie die Nummer , dann hol dir den zweiten STDOUT-Stream. Wenn die Kommentarzeile jedoch nicht kommentiert ist, wird die Seite blockiert.

Gibt es eine Möglichkeit, das Problem zu lösen? Wie kann ich aus der Pipe lesen, obwohl nicht alle Daten dort abgelegt wurden? Oder gibt es eine bessere Möglichkeit, diese Art von interaktivem Programm zu schreiben?

+0

Ist Ihr "user" die Interaktion von Website beendet? Denn auf diese Weise scheint der Benutzer keinen direkten Zugriff auf den STDIN Ihres Servers zu haben. – Passerby

+0

@Passerby Der Benutzer drückt eine Schaltfläche zum Kompilieren und gibt _x_ ein und sendet sie an den Server. Aber bevor er _x_ eingibt, muss der Server zuerst den Stream von "STDIN" holen und an die Website senden, so dass der Benutzer weiß, dass er _x_ eingeben soll.Das Problem ist, dass der Server den Stream in diesem Moment nicht bekommen kann. – dahui

Antwort

18

Es ist mehr ein C oder ein glibc Problem. Sie müssen fflush(stdout) verwenden.

Warum? Und was ist der Unterschied zwischen der Ausführung von a.out in einem Terminal und dem Aufruf von PHP?

Antwort: Wenn Sie a.out in einem Terminal ausführen (was stdin a tty ist), wird die glibc Linie gepufferte IO verwenden. Aber wenn Sie es von einem anderen Programm ausführen (PHP in diesem Fall) und stdin ist eine Pipe (oder was auch immer, aber kein tty) als die glibc interne IO-Pufferung verwenden wird. Deshalb werden die ersten fgets() Blöcke unkommentiert. Für weitere Informationen überprüfen Sie diese article.

Gute Nachrichten: Sie können diese Pufferung mit dem Befehl stdbuf steuern. Ändern $run_string zu:

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

hier ein funktionierendes Beispiel kommt. Arbeiten, auch wenn der C-Code tun kümmert sich um nicht fflush(), da sie die stdbuf Befehl verwendet:

subprocess Start

$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() 
); 

Satz alle Ströme auf nicht blockierenden Modus

// 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'); 
} 

Kind pid bekommen . wir brauchen sie später

// 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']; 

Umfrage, bis das Kind

// 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); 
    } 
} 
+1

Danke ~ Aber der C-Code ist vom Benutzer, und er drückt nur "Enter" zum Kompilieren und Ausführen. Ist es möglich, einen Teil von _test.php_ zu ändern, damit es funktioniert? – dahui

+0

Interessantes Problem! :) Wird dies untersuchen. – hek2mgl

+0

Vielen Dank! Es hat mich schon lange gestört. – dahui

Verwandte Themen