2013-05-03 104 views
7

我想讓一個網站可以在線編譯和運行他們的代碼,因此我們需要找到用戶發送指令的交互方式。使用proc_open從STDIN管道讀取

其實,首先想到的是exec()system(),但是當用戶想輸入某些東西時,這種方式是行不通的。所以我們必須使用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流,並輸入數量,然後獲得第二個STDOUT流。但是,如果我將註釋行取消註釋,則頁面將被阻止。

有沒有辦法解決這個問題?我怎樣才能從管道讀取數據,而並非所有的數據都放在那裏?還是有更好的方式來編寫這種交互式程序?

+0

是您的「用戶」,從網站互動?因爲通過這種方式,用戶似乎不能直接訪問服務器的'STDIN'。 – Passerby 2013-05-03 04:13:19

+0

@Passerby用戶按下按鈕進行編譯,並輸入_x_並將其發送到服務器。但是在輸入_x_之前,服務器必須首先從'STDIN'獲取流並將其發送到網站,以便用戶知道他應該輸入_x_。問題是服務器無法在那一刻得到流.. – dahui 2013-05-03 04:39:52

回答

18

它更多是Cglibc問題。你將不得不使用fflush(stdout)

爲什麼?在終端中運行a.out並從PHP中調用它有什麼區別?

答案:如果你在終端運行a.out(是stdin a tty),那麼glibc將使用線路緩衝的IO。但是如果你從另一個程序(在這種情況下是PHP)運行它,並且它的標準輸入是一個管道(或者其他什麼,但不是一個tty)比glibc將使用內部IO緩衝。這就是爲什麼第一個fgets()塊如果未註釋。欲瞭解更多信息,請點擊這裏article

好消息:您可以使用stdbuf命令控制此緩衝。更改$run_string到:

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

這裏來工作的例子。工作,即使它是使用stdbuf命令C代碼不關心fflush()

啓動子

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

謝謝〜但C代碼來自用戶,他只是按下'Enter'來編譯和運行。是否有可能改變_test.php_的某些部分使其工作? – dahui 2013-05-03 04:34:33

+0

有趣的問題! :)將調查這一點。 – hek2mgl 2013-05-03 04:36:17

+0

非常感謝!它困擾了我很長一段時間.. – dahui 2013-05-03 04:43:28