2013-05-03 149 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