我正在尝试建立一个人们可以在线编译和运行代码的网站,因此我们需要找到一种用户发送指令的交互方式.
实际上,首先想到的是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流.但如果我将注释行取消注释,该页面将被阻止.
它更像是C或glibc问题.你必须使用fflush(stdout).
答:如果你在终端中运行a.out(正在使用stdin),那么glibc将使用行缓冲IO.但是如果你从另一个程序运行它(在这种情况下为PHP)并且它的stdin是一个管道(或者其他什么但不是tty),而glibc将使用内部IO缓冲.这就是为什么第一个fgets()如果没有注释就会阻塞.有关更多信息,请查看此article.
好消息:您可以使用stdbuf
命令控制此缓冲.将$run_string更改为:
$run_string = "cd ".$addr_base.";stdbuf -o0 ./a.out 2>&1";
这是一个有效的例子.即使C代码不关心fflush(),因为它使用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,$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,$utv); if($n > 0) { $input = fread(STDIN,8092); // inpput to program fwrite($pipes[0],$input); } }