Чтение из канала STDIN при использовании proc open


Я пытаюсь создать веб-сайт, на котором люди могли бы компилировать и запускать свой код онлайн, поэтому нам нужно найти интерактивный способ для пользователей отправлять инструкции.

На самом деле, первое, что приходит на ум, это exec() или system(), но когда пользователи хотят ввести sth, этот способ не сработает. Поэтому мы должны использовать 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 и ввести номер, затем получите второй поток стандартного вывода. Но если у меня есть закомментированная строка без комментариев, страница будет заблокирована.

Есть ли способ решить эту проблему? Как я могу читать из канала, пока не все данные были помещены туда? Или есть лучший способ написать такую интерактивную программу?

Author: dahui, 2013-05-03

1 answers

Это скорее проблема C или glibc. Вам придется использовать fflush(stdout).

Почему? И в чем разница между запуском a.out в терминале и вызовом его из PHP?

Ответ: Если вы запустите a.out в терминале (будучи стандартным в tty), то glibc будет использовать линейный буферизованный ввод-вывод. Но если вы запустите его из другой программы (в данном случае PHP), и это stdin -канал (или что-то еще, но не tty), то glibc будет использовать внутреннюю буферизацию ввода-вывода. Вот почему первый fgets() блокирует, если без комментариев. Для получения дополнительной информации ознакомьтесь с этой статьей .

Хорошие новости: Вы можете управлять этой буферизацией с помощью 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, $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);
    }
}
 18
Author: hek2mgl, 2013-05-04 08:10:31