使用proc_open时从STDIN管道读取 [英] Reading from STDIN pipe when using proc_open

查看:115
本文介绍了使用proc_open时从STDIN管道读取的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试建立一个人们可以在线编译和运行其代码的网站,因此我们需要找到一种交互式的方式来使用户发送指令.

I am trying to make a website where people can compile and run their code online, thus we need to find an interactive way for users to send instructions.

实际上,首先想到的是exec()system(),但是当用户想要输入sth时,这种方式将行不通.所以我们必须使用proc_open().

Actually, what first comes to mind is exec() or system(), but when users want to input sth, this way won't work. So we have to use 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流.但是,如果我的注释行未注释,则该页面将被阻止.

When running the C code, I want to get the first STDOUT stream, and input the number, then get the second STDOUT stream. But if I have the commented line uncommented, the page will be blocked.

是否有解决问题的方法?在并非所有数据都已放置在那里的情况下,如何从管道中读取数据?还是有更好的方法来编写这种交互式程序?

Is there a way to solve the problem? How can I read from the pipe while not all data has been put there? Or is there a better way to write this kind of interactive program?

推荐答案

更多的是Cglibc问题.您必须使用fflush(stdout).

It is more a C or a glibc problem. You'll have to use fflush(stdout).

为什么?在终端中运行a.out和从PHP调用它之间有什么区别?

Why? And what's the difference between running a.out in a terminal and calling it from PHP?

答案:如果您在终端中运行a.out(作为tty的stdin),则glibc将使用行缓冲的IO.但是,如果您从另一个程序(在本例中为PHP)运行它,并且它的stdin是一个管道(或其他但不是tty的管道),则glibc将使用内部IO缓冲.这就是为什么第一个fgets()如果未注释就阻塞的原因.有关更多信息,请参见文章.

Answer: If you run a.out in a terminal (being stdin a tty) then the glibc will use line buffered IO. But if you run it from another program (PHP in this case) and it's stdin is a pipe (or whatever but not a tty) than the glibc will use internal IO buffering. That's why the first fgets() blocks if uncommented. For more info check this article.

好消息:您可以使用 stdbuf 命令来控制此缓冲.将$run_string更改为:

Good news: You can control this buffering using the stdbuf command. Change $run_string to:

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


这是一个可行的例子.即使C代码不关心fflush()也可以正常工作,就像使用stdbuf命令一样:


Here comes a working example. Working even if the C code don't cares about fflush() as it is using the stdbuf command:

开始子过程

$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 blocking mode

// 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 child pid. we need it later

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

这篇关于使用proc_open时从STDIN管道读取的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆