2013-05-03 5 views
7

Я пытаюсь создать сайт, на котором люди могут компилировать и запускать свой код в Интернете, поэтому нам нужно найти интерактивный способ отправки пользователями инструкций.Чтение из трубы 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, и введите номер , затем получите второй поток STDOUT. Но если у меня есть прокомментированная строка без комментариев, страница будет заблокирована.

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

+0

Ваш «пользователь», взаимодействующих с веб-сайта? Потому что таким образом у пользователя нет прямого доступа к 'STDIN' вашего сервера. – Passerby

+0

@Passerby Пользователь нажимает кнопку для компиляции и вводит _x_ и отправляет ее на сервер. Но прежде чем он введет _x_, сервер должен сначала получить поток из «STDIN» и отправить его на сайт, чтобы пользователь знал, что он должен ввести _x_.Проблема в том, что сервер не может получить поток в этот момент. – dahui

ответ

18

Больше C или проблема glibc. Вам нужно будет использовать fflush(stdout).

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

Ответ: Если вы запустили a.out в терминале (будучи stdin tty), тогда glibc будет использовать строку с буферизацией ввода-вывода. Но если вы запустите его из другой программы (в этом случае PHP), и это stdin - это канал (или что-то другое, но не tty), чем glibc будет использовать внутреннюю буферизацию ввода-вывода. Вот почему первые 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, $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'); 
} 

получить дочерний процесс , нам нужно это позже

// 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

+0

Интересная проблема! :) Расследую это. – hek2mgl

+0

Большое спасибо! Это беспокоило меня в течение долгого времени. – dahui