Я пытаюсь создать сайт, на котором люди могут компилировать и запускать свой код в Интернете, поэтому нам нужно найти интерактивный способ отправки пользователями инструкций.
Фактически, первое, что приходит в голову, это 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. Но если у меня есть прокомментированная строка без комментариев, страница будет заблокирована.
Есть ли способ решить проблему? Как я могу читать из трубы, пока не все данные были там поставлены? Или есть лучший способ написать такую интерактивную программу?
Это скорее проблема C
или glibc
. Вам нужно будет использовать fflush(stdout)
.
Зачем? И в чем разница между запуском a.out
в терминале и вызовом его из PHP?
Ответ. Если вы запустите a.out
в терминале (будучи stdin 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); } }