PHP proc_open блокирует чтение на стандартный вывод до завершения программы C

Я работаю над приложением, которое использует сканер отпечатков пальцев для идентификации пользователя. Взаимодействие со сканером отпечатков пальцев осуществляется в программе на C с libfprint, которую я вызываю с помощью proc_open в PHP. Процесс регистрации отпечатка пальца является многоступенчатым процессом, который обрабатывается libprintf как конечный автомат; управление передается между библиотекой и C-программой, чтобы позволить C-программе обеспечивать обратную связь с пользователем (этап успешен, неуспешен и почему) …

Проблема возникает при попытке прочитать данные из ресурса процесса в PHP. Пример кода PHP здесь:

<?php
ob_implicit_flush(true);
$cmd = "/home/pi/projects/PingPongSet/enroll";

$descriptorspec = array(
0 => array("pipe", "r"),   // stdin is a pipe that the child will read from
1 => array("pipe", "w"),   // stdout is a pipe that the child will write to
2 => array("pipe", "w")    // stderr is a pipe that the child will write to
);
flush();
$process = proc_open($cmd, $descriptorspec, $pipes);
echo 'opened'."\n";
if (is_resource($process)) {
sleep(1);
echo "blah\n";
var_export(fgets($pipes[1]));
echo "blah213\n";
while ($s = fgets($pipes[1])) {
print $s;
flush();
}
}

?>

Я добавил var_export, чтобы сузить место, где происходила блокировка. Я получаю «открытые» и «бла» выходные данные нормально, но «бла123» не приходит, пока я не закончу процесс регистрации со сканером отпечатков пальцев, после чего все выходные данные со сканера отпечатков пальцев приходят вместе с ним. Приложение enroll.c в значительной степени является примером enroll.c, который поставляется с libfprint, с несколькими модификациями для воспроизведения звуков, когда конкретный этап успешен. «Важный» фрагмент этой программы здесь:

struct fp_print_data *enroll(struct fp_dev *dev) {
struct fp_print_data *enrolled_print = NULL;
int r;

do {
struct fp_img *img = NULL;

sleep(1);
printf("\nScan your finger now.\n");

r = fp_enroll_finger_img(dev, &enrolled_print, &img);
printf("\nFinger scanned.\n");
if (img) {
fp_img_save_to_file(img, "enrolled.pgm");
printf("Wrote scanned image to enrolled.pgm\n");
fp_img_free(img);
}
if (r < 0) {
printf("Enroll failed with error %d\n", r);
play_error();
return NULL;
}

switch (r) {
case FP_ENROLL_COMPLETE:
printf("Enroll complete!\n");
break;
case FP_ENROLL_FAIL:
printf("Enroll failed, something wen't wrong :(\n");
play_error();
return NULL;
case FP_ENROLL_PASS:
printf("Enroll stage passed. Yay!\n");
play_success();
break;
case FP_ENROLL_RETRY:
printf("Didn't quite catch that. Please try again.\n");
play_error();
break;
case FP_ENROLL_RETRY_TOO_SHORT:
printf("Your swipe was too short, please try again.\n");
play_error();
break;
case FP_ENROLL_RETRY_CENTER_FINGER:
printf("Didn't catch that, please center your finger on the ""sensor and try again.\n");
play_error();
break;
case FP_ENROLL_RETRY_REMOVE_FINGER:
printf("Scan failed, please remove your finger and then try ""again.\n");
play_error();
break;
}
} while (r != FP_ENROLL_COMPLETE);

if (!enrolled_print) {
fprintf(stderr, "Enroll complete but no print?\n");
return NULL;
}

printf("Enrollment completed!\n\n");
play_success();
return enrolled_print;
}

Вот вывод приложения PHP после завершения программы на C:

opened
blah
-----NOTE: THE STUFF BELOW HERE DOESN'T DISPLAY UNTIL AFTER enroll TERMINATES-----
'Found device claimed by Digital Persona U.are.U 4000/4000B/4500 driver
'blah213
Opened device. It's now time to enroll your finger.Scan your finger now.
uru4000:info [init_run_state] Versions 0040 and 0014

Finger scanned.
Wrote scanned image to enrolled.pgm
Enroll stage passed. Yay!

Scan your finger now.

Finger scanned.
Wrote scanned image to enrolled.pgm
Enroll stage passed. Yay!

Scan your finger now.

Finger scanned.
Wrote scanned image to enrolled.pgm
Enroll stage passed. Yay!

Scan your finger now.

Finger scanned.
Wrote scanned image to enrolled.pgm
Enroll stage passed. Yay!

Scan your finger now.

Finger scanned.
Wrote scanned image to enrolled.pgm
Enroll complete!
Enrollment completed!

Closing device

Любые идеи относительно того, почему fgets() позвонить (тоже пробовал fread() а также stream_get_contents() с аналогичным поведением) блокирует, пока программа C не завершится? Я ожидаю получить вывод во время работы программы. Если я перейду на использование $cmd = "ping 127.0.0.1";, он ведет себя так, как я и ожидал, выводя каждую строку пинга по мере его вывода на стандартный вывод.

Обновление … фиксированный

Barmar был прав ниже, мне просто пришлось отключить буферизацию вывода … но при этом я столкнулся с некоторыми странными несоответствиями, которые я хотел задокументировать для тех, у кого есть подобные проблемы …
Исправление было

setbuf(stdout, NULL);

который согласно этот комментарий должен быть эквивалентным

setvbuf(stdout, NULL, _IONBF, 0);

но с setvbuf(stdout, NULL, _IONBF, 0);, программа C выдаст все просто отлично, вместе с setvbuf(stdout, NULL, _IONBF, 0);. С setbuf(stdout, NULL);все работало отлично.

Что я нахожу очень интересным, так это то, что с помощью программы, подобной следующей, PHP-скрипт смог без проблем получить stdio, не отключая буферизацию вывода … и оба сценария включали stdio, поэтому в обоих должны были быть включены буферизация вывода .. ,

#include <stdio.h>
#include <stdlib.h>
#include <alsa/asoundlib.h>
#include <alsa/pcm.h>

int main() {
int val;

printf("ALSA library version: %s\n", SND_LIB_VERSION_STR);

printf("\nPCM stream types:\n");
for (val = 0; val <= SND_PCM_STREAM_LAST; val++)
printf("  %s\n",
snd_pcm_stream_name((snd_pcm_stream_t)val));

return 0;
}

Во всяком случае, это сейчас исправлено. Спасибо!

0

Решение

Бармар был прав в том, что было причиной проблемы: stdio был в буфере. Это то, что исправило это для меня:

setbuf(stream, NULL);
0

Другие решения

Других решений пока нет …