上一篇文章中,我们讲解了利用popen函数进行匿名管道通信,不过有个问题,就是该函数只能实现单向通信,也就是同时只能打开读通道或者写通道。今天要介绍的函数 proc_open 则可以实现双向通信。

在观看本文章之前,请先去了解下函数 proc_open 的基本用法,链接地址:proc_open

首先,我们创建 pipe-process.php 文件,内容如下:

<?php

$pid = getmypid();

echo '子进程PID:'.$pid.'|父进程数据:'.fgets(STDIN)."\n";

fwrite(STDERR, '我是从错误信息');

很简单,从标准输入读取数据,然后输出,另外写入一段数据到标准错误中。

然后,创建 pipe2.php 文件,内容如下:

<?php

$pid = getmypid();
echo "PID:{$pid}\n";

// 需要执行的命令
$command = '/usr/local/opt/php@7.2/bin/php pipe-process.php';

// 命令执行的目录
$cwd = '/Users/xiaoteng/work/test/';

$process = proc_open($command, [
    // 标准输入
    0 => ['pipe', 'r'],
    // 标准输出
    1 => ['pipe', 'w'],
    // 标准错误流
    2 => ['file', '/tmp/pipe2.log', 'a']
], $pipes, $cwd);

// 将数据传递给子进程
fwrite($pipes[0], '来自父进程的消息,当前时间:'.date('Y-m-d H:i:s')."\n");
fclose($pipes[0]);

// 读取子进程的标准输出
$line = fgets($pipes[1]);
fclose($pipes[1]);
echo "读取到子进程的消息,内容:\n";
echo $line."\n";

proc_close($process);

简单解析下内容。通过 proc_open 函数创建一个子进程,这个子进程就是 php pipe-process.php 这个命令。在该函数的第二个参数中,我们指定了标准描述符,其中0,1,2分别代表的是标准输入,标准输出,标准错误,这个都是通用的。

这里的话,我们将0,1也就是标准输入和标准输出指定为 pipe 也就是管道,因为我们还需要通过管道通信。然后将2也就是标准错误的处理类型指定为file,也就是说,子进程的执行错误输出将会保存到这个file中,在这里的话,也就是 /tmp/pipe2.log 这个文件中。至于该参数中的 r, w, a这些其实熟悉文件操作的小伙伴应该都知道的吧,不知道的话可以看下 fopen 这个函数。

proc_open 函数的第三个参数,也就是这里的 $pipes ,这是个引用变量,在函数执行后,该变量是标准描述符的具体的文件指针,其中 $pipes[0] 是标准输入的指针, $pipes[1] 是标准输出的指针。

OK,我们来执行下:

~ php pipe2.php
PID:84980
读取到子进程的消息,内容:
子进程PID:84981|父进程数据:来自父进程的消息,当前时间:2020-08-08 09:48:28

可以看到,子进程成功的接收到父进程的消息,父进程成功的读取到了子进程的输出。这样的话,我们就完成了一个双向的通信。我们在来看下标准错误输出:

~ cat /tmp/pipe2.log
我是从错误信息

可以看到,在子进程中写入标准错误的数据被保存到了 /tmp/pipes.log 这个文件中了。

这就是匿名管道的双向通信。可以看到的是,proc_open 函数相比较 popen 更加强大。