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

管道是什么?

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

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

echo "hello" | grep "he"

为了更好地理解,我们可以将其重写为:

(echo "hello") | (grep "he")

# 这里,“echo” 和 “grep” 都是可执行的二进制程序。
# | 符号表示管道,它的作用是将前一个命令的输出作为后一个命令的输入。

指令的执行流程

  1. 创建子进程:shell创建一个新的子进程来执行 echo “hello” 命令;
  2. 标准输出:二进制文件echo会根据接收到的参数”hello”将字符串 “hello” 写入其标准输出, 这就是echo要做的事;
  3. 管道传递:shell通过管道将 “hello” 从 echo 的标准输出传递到 grep 的标准输入;
  4. grep处理:grep 从管道读取 “hello” 并检查是否包含模式 “he”;
  5. 输出结果:如果匹配成功,grep 将匹配的结果渲染并输出到标准输出;

大多数 shell(如 bash 或 zsh)都支持管道功能。通过管道,用户可以构建复杂的命令序列来完成特定任务;

它与PHP有何关系?

现在我们来看一个具体的示例:

(echo "<?php sleep(1); echo 'hello,world';") | (php)

根据上面的执行流程,我们可以将这个命令分解为以下步骤:

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

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

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

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

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

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

(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这个函数了呢?

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

相关项目

github.com/cloudtay/p-ripple-core

相关文章


原创声明: 原创(转载请保留文章主体)

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 4周前 自动加精
讨论数量: 5

发现真正讨论技术的文章,往往没人评论。这篇文章,文风很好,内容虽然冷门,但也写得挺好。

1个月前 评论
cclilshy (楼主) 1个月前

写的很好,laravel 框架队列底层也是用的 proc_open 去处理执行反序列化后的php代码的👍🏻

3周前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!