Недавно я попытался связаться с двоичным файлом на моем веб-сервере Ubuntu [1], используя функцию PHP proc_open. Я могу установить соединение и определить трубы STDIN, STDOUT и STDERR. Ницца.
Теперь бинар, с которым я говорю, является интерактивным программным обеспечением для компьютерной алгебры, поэтому я хотел бы сохранить как STDOUT, так и STDIN после первой команды, чтобы я мог использовать приложение несколькими строками позже в интерактивном режиме (прямые пользовательские входы из веб-интерфейс).
Однако, как выясняется, функции PHP для чтения STDOUT двоичного файла (либо stream_get_contents, либо fgets) нуждаются в закрытом STDIN, прежде чем они смогут работать. В противном случае программа блокируется.
Это серьезный недостаток, так как я не могу просто снова открыть закрытый STDIN после его закрытия. Поэтому мой вопрос: почему мой сценарий тупик, если я хочу прочитать STDOUT, когда мой STDIN все еще жив?
Благодарю Йенса
[1] proc_open возвращает false, но не записывает в файл ошибки – разрешения – проблемы?
мой источник:
$descriptorspec = array( 0 => array("pipe","r"), 1 => array("pipe","w"), 2 => array("file","./error.log","a") ) ; // define current working directory where files would be stored $cwd = './' ; // open reduce $process = proc_open('./reduce/reduce', $descriptorspec, $pipes, $cwd) ; if (is_resource($process)) { // some valid Reduce commands fwrite($pipes[0], 'load excalc; operator x; x(0) := t; x(1) := r;'); // if the following line is removed, the script deadlocks fclose($pipes[0]); echo "output: " . stream_get_contents($pipes[1]); // close pipes & close process fclose($pipes[0]); fclose($pipes[1]); fclose($pipes[2]); proc_close($process); }
РЕДАКТИРОВАТЬ:
Этот код работает. Это потому, что он использует пропуски, чтобы дождаться, когда незаблокированные STDOUT будут заполнены данными. Как мне сделать это более элегантно?
@ Elias: путем опроса записи $ status ['running'] вы можете определить только, работает ли весь процесс, но нет, если процесс занят или работает на холостом ходу … Вот почему я должен включать эти прошивки.
define('TIMEOUT_IN_MS', '100'); define('TIMEOUT_STEPS', '100'); function getOutput ($pipes) { $result = ""; $stage = 0; $buffer = 0; do { $char = fgets($pipes[1], 4096); if ($char != null) { $buffer = 0; $stage = 1; $result .= $char; } else if ($stage == "1") { usleep(TIMEOUT_IN_MS/TIMEOUT_STEPS); $buffer++; if ($buffer > TIMEOUT_STEPS) { $stage++; } } } while ($stage < 2); return $result; } $descriptorspec = array( 0 => array("pipe", "r"), 1 => array("pipe", "w") ) ; // define current working directory where files would be stored $cwd = './' ; // open reduce $process = proc_open('./reduce/reduce', $descriptorspec, $pipes, $cwd); if (is_resource($process)) { stream_set_blocking($pipes[1], 0); echo "startup output:<br><pre>" . getOutput($pipes) . "</pre>"; fwrite($pipes[0], 'on output; load excalc; operator x; x(0) := t; x(1) := r;' . PHP_EOL); echo "output 1:<br><pre>" . getOutput($pipes) . "</pre>"; fwrite($pipes[0], 'coframe o(t) = sqrt(1-2m/r) * dt, o(r) = 1/sqrt(1-2m/r) * dr with metric g = -o(t)*o(t) + o(r)*o(r); displayframe;' . PHP_EOL); echo "output 2:<br><pre>" . getOutput($pipes) . "</pre>"; // close pipes & close process fclose($pipes[0]); fclose($pipes[1]); fclose($pipes[2]); proc_close($process); }
Это напоминает мне сценарий, который я написал некоторое время назад . Хотя это может послужить вам источником вдохновения (или другими), оно не делает то, что вам нужно. Он содержит пример того, как вы можете читать вывод потока, не закрывая ни один из потоков.
Возможно, вы можете применить ту же логику к своей ситуации:
$allInput = array( 'load excalc; operator x; x(0) := t; x(1) := r;' );//array with strings to pass to proc if (is_resource($process)) { $output = ''; $input = array_shift($allInput); do { usleep(200);//make sure the running process is ready fwrite( $pipes, $input.PHP_EOL,//add EOL strlen($input)+1 ); fflush($pipes[0]);//flush buffered data, write to stream usleep(200); $status = proc_get_status($process); while($out = fread($pipes[1], 1024) && !feof($pipes[1])) $output .= $out; } while($status['running'] && $input = array_shift($allInput)); //proc_close & fclose calls here }
:$allInput = array( 'load excalc; operator x; x(0) := t; x(1) := r;' );//array with strings to pass to proc if (is_resource($process)) { $output = ''; $input = array_shift($allInput); do { usleep(200);//make sure the running process is ready fwrite( $pipes, $input.PHP_EOL,//add EOL strlen($input)+1 ); fflush($pipes[0]);//flush buffered data, write to stream usleep(200); $status = proc_get_status($process); while($out = fread($pipes[1], 1024) && !feof($pipes[1])) $output .= $out; } while($status['running'] && $input = array_shift($allInput)); //proc_close & fclose calls here }
Теперь, видя, что я не знаю, что именно вы пытаетесь сделать, этот код нужно будет немного изменить. Например, вы можете установить, что трубы STDIN и STDOUT не блокируются.
Это просто добавить это, сразу после вызова proc_open
:
stream_set_blocking($pipes[0], 0); stream_set_blocking($pipes[1], 0);
Играйте вокруг, получайте удовольствие, и, возможно, дайте мне знать, был ли этот ответ полезен каким-либо образом …
Я предполагаю, что вы все делаете правильно, за исключением того, что двоичный код никогда не уведомляется о том, что он получил все входные данные и может начать работать. Закрывая STDIN, вы начинаете работу, потому что ясно, что больше не будет ввода. Если вы не закрываете STDIN, двоичный файл ожидает большего ввода, а ваша сторона ждет его выхода.
Вероятно, вам нужно закончить ввод с помощью новой строки или любого другого действия протокола от вас. Или, возможно, закрытие STDIN – это действие, которое ожидается от вас. Если процесс специально не создан, чтобы оставаться открытым и продолжать поток ввода, вы не можете заставить его это делать. Если процесс считывает все входные данные, обрабатывает его, возвращает выход и затем завершает работу, вы не можете заставить его оставаться в живых, чтобы обработать больше ввода позже. Если процесс явно поддерживает это поведение, должно быть определение того, как вам нужно разграничить ваш ввод.