PHP proc_open阻塞读取到stdout直到C程序终止

时间:2014-09-19 03:44:32

标签: php c blocking proc-open

我正在开发一款使用指纹扫描仪进行用户识别的应用。使用libfprint在C程序中完成与指纹扫描程序的交互,我使用PHP中的proc_open调用libfprint。指纹登记过程是由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();
    }
}

&GT;

我添加了var_export以缩小阻塞发生的位置。我得到“打开”和“等待”输出正常,但是直到我用指纹扫描仪完成注册过程后才会出现“blah123”,此时指纹扫描仪的所有输出都随之出现。 enroll.c应用程序几乎就是libfprint附带的enroll.c示例,在特定阶段成功时进行了一些修改以播放声音。该计划的“重要”部分在这里:

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;
}

以下是C程序终止后PHP应用程序的输出:

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";,它的行为与我期望的一样,在输出到stdout时输出ping的每一行。

更新...固定

Barmar在下面是正确的,我只需要关闭输出缓冲....但是这样做,我遇到了一些奇怪的不一致,我想为其他有类似问题的人记录... 修复是

setbuf(stdout, NULL);

根据this comment 等同于

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;
}

无论如何,现在已经修好了。谢谢!

1 个答案:

答案 0 :(得分:0)

Barmar对导致问题的原因是正确的:stdio正在被缓冲。这就是为我解决的问题:

setbuf(stream, NULL);