PHP高性能网络编程之管道(1)


925

管道是什么?
管道(Pipe)是一种用于连接两个或多个命令的标准工具,使得前一个命令的输出成为后一个命令的输入。虽然管道这个词对一些人来说可能有些陌生,但在日常编程工作中,尤其是使用 Linux 或 Unix
shell 时,管道是非常常见的。

例如,考虑下面这条命令:

echo “hello” | grep “he”
为了更好地理解,我们可以将其重写为:

(echo “hello”) | (grep “he”)

这里,“echo” 和 “grep” 都是可执行的二进制程序。

| 符号表示管道,它的作用是将前一个命令的输出作为后一个命令的输入。

指令的执行流程
创建子进程:shell 创建一个新的子进程来执行 echo “hello” 命令;
标准输出:二进制文件 echo 会根据接收到的参数”hello” 将字符串 “hello” 写入其标准输出,这就是 echo 要做的事;
管道传递:shell 通过管道将 “hello” 从 echo 的标准输出传递到 grep 的标准输入;
grep 处理:grep 从管道读取 “hello” 并检查是否包含模式 “he”;
输出结果:如果匹配成功,grep 将匹配的结果渲染并输出到标准输出;
大多数 shell(如 bash 或 zsh)都支持管道功能。通过管道,用户可以构建复杂的命令序列来完成特定任务;

它与 PHP 有何关系?
现在我们来看一个具体的示例:

(echo “<?php sleep(1); echo ‘hello,world’;”) | (php)
根据上面的执行流程,我们可以将这个命令分解为以下步骤:

创建子进程:shell 创建一个新的子进程来执行 echo “<?php sleep (1); echo ‘hello,world’;” 命令。

脚本输出:echo 命令将 PHP 脚本输出到标准输出。

管道传递:shell 使用管道将这个 PHP 脚本作为输入传递给 php 命令。

PHP 解析:php 解释器读取管道中的 PHP 脚本,并执行其中的代码。

输出结果:由于 PHP 脚本中包含了 echo ‘hello,world’;,所以 php 解释器会输出 “hello,world”。

接下来,我们尝试一个不同的示例:

(echo “<?php sleep(1); echo ‘hello,world’;”) | (php -v)
结果分析:

在这种情况下 php -v 输出 PHP 版本信息而不是执行脚本。
这是因为 php -v 是一个整体命令,用于显示 PHP 的版本信息。

为什么不执行管道中的指令呢?
事实上这不是必然性的,这只是二进制文件 php 的决定

当 php 命令带有参数时,它不会从标准输入读取任何内容,而是直接按照另一种自行决定的方式执行

PHP 中的管道
PHP 允许使用 proc_open 函数来创建一个进程控制块,这个进程控制块拥有独立的输入输出流,可以用来实现管道的功能

<?php

//$descriptorspec的为固定结构,这是它的设计者指明的

$descriptorspec = [
0 => [‘pipe’, ‘r’], // 标准输入
1 => [‘pipe’, ‘w’], // 标准输出
2 => [‘pipe’, ‘w’], // 标准错误
];

$process = proc_open(‘/bin/sh’, $descriptorspec, $pipes);

if (is_resource($process)) {
fwrite($pipes[0], ‘echo “hello,world”‘);
fclose($pipes[0]);

echo stream_get_contents($pipes[1]);
fclose($pipes[1]);

proc_close($process);

}
通过上述简单例子我们可以直接使用 PHP 来实现管道的功能,直接模拟一个终端会话,甚至能自己实现渲染并实现一个类似于 zsh 一样的 shell 管理工具
大胆点想我们是不是可能直接访问 PHP 二进制文件打开一个 PHP 进程,然后通过管道的方式来执行 PHP 脚本呢?

PHP 中打开 PHP 进程
<?php

//$descriptorspec的为固定结构,这是它的设计者指明的

$descriptorspec = [
0 => [‘pipe’, ‘r’], // 标准输入
1 => [‘pipe’, ‘w’], // 标准输出
2 => [‘pipe’, ‘w’], // 标准错误
];

$process = proc_open(PHP_BINARY, $descriptorspec, $pipes);

if (is_resource($process)) {
fwrite($pipes[0], file_get_contents(‘test.php’));
fclose($pipes[0]);

echo stream_get_contents($pipes[1]);
fclose($pipes[1]);

}
是的,上面的代码会如我们所想的那样,打开一个 PHP 进程,并通过管道的方式执行 test.php 内的脚本代码

是不是有种发现新大陆的感觉:掌握了这个方法是不是可以离开 shell_exec 这个函数了呢?

没错,它完全可以做到,但如果需要自己来管理输入输出流,这在复杂的管道操作中会变得非常麻烦


发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注