PHP 不会打开 fifo 进行写入

PHP won't open fifo for writing

我正在围绕一个复杂的二进制文件编写一个小包装器(在 PHP 7.0.33 中),它从一个命名文件中获取输入。由于这将是处理秘密,我不想将数据提交到文件系统,因此使用 FIFO 而不是传统文件。二进制文件会很高兴地从 FIFO 中读取数据(使用 2 shell 会话进行测试 - 在第一个会话中我创建了 fifo 并启动了二进制文件,在第二个会话中我将一个文件放入了 fifo)。

但是在 PHP 中,对 fopen() 的调用会阻塞,无论我指定的是 w、a 还是 c

  if (posix_mkfifo("myfifo", 0600)) {
     $writer=fopen("myfifo", 'w'); // this blocks
     `binary myfifo`;
     fputs($writer, $mydata);
  }

虽然我希望在没有任何数据读取时写入会阻塞,但我没想到 fopen() 会阻塞。

使用“w+”似乎确实有效(执行继续,二进制文件启动)但是二进制文件失败并显示

 QIODevice::read (QFile, "filename"): device not open

为了进一步调查,我为二进制文件编写了一个简单的替代品。当我将文件放入 FIFO 时,这再次起作用:

$in='';
$fh=fopen($argv[1],'r');
if (is_resource($fh)) {
        print "File opened\n";
        while (!feof($fh)) {
                $in.=fgets($fh);
        }
} else {
        print "failed to open file\n";
}

file_put_contents("output", $in);

但是当我从 PHP 代码写入 FIFO 时....

fopen(import): failed to open stream: No such file or directory in ...

默认情况下,打开一个 FIFO 将阻塞,直到至少有一个 reader 和 writer。这样做的理由是,如果没有进程可以使用它,内核就没有地方可以存储管道数据。 fifo 的手册页:

... the FIFO special file has no contents on the file system, the file system entry merely serves as a reference point so that processes can access the pipe using a name in the file system.

The kernel maintains exactly one pipe object for each FIFO special file that is opened by at least one process. The FIFO must be opened on both ends (reading and writing) before data can be passed. Normally, opening the FIFO blocks until the other end is opened also.

不过您可以绕过此行为。一种方法就像您所做的那样 - 自己打开读写端。另一种是在打开文件时设置 O_NONBLOCK 标志(之后可以设置回阻塞)。 AFAIK 你不能用 fopen 做到这一点。 dio 库示例:

<?php
echo "Opening\n";
$writer = dio_open("myfifo", O_CREAT | O_WRONLY | O_NONBLOCK) or die("Could not create FIFO\n");
echo "Open. Writing\n";
dio_write($writer, "DATA");
echo "Done\n";

这样做的注意事项是,如果没有reader,上面的过程将写入数据,然后立即退出,然后数据将永远丢失。

为了任何在 Google 中找到此问题并寻求比对 semisecure 的回答发表评论更详细的解决方案的人的利益:

if (pcntl_fork()) {
    `binary myfifo`;
} else {
    $fh=fopen('myfifo', 'w');
    fputs($fh, $data);
    fclose($fh);
}

(但您可能还想在编写器上添加一个 SIGALRM,以防“二进制”不执行/刷新管道)。