【问题标题】:How to wait for a process executed by proc_open() in php?如何等待由 proc_open() 在 php 中执行的进程?
【发布时间】:2014-01-25 17:18:11
【问题描述】:

请阅读此问题后,不要说它是复制的。我已经在网上搜索过,但没有一个解决方案适合我。

我在做什么:->
我通过在本地主机上运行 apache 服务器通过 php 获取源文件(例如 .c)。我将该文件保存在“/code/”目录中,并使用 php 中的 proc_open() 编译和执行该源文件。现在我想要等待主php脚本直到“proc_open()”创建的进程终止,所以我使用了“pcntl_waitpid()”。但我认为使用“pcntl_waitpid()”存在问题,因为“pcntl_waitpid()”之后的脚本没有执行。 由“pcntl_waitpid()”创建的进程从“/code/input.txt”文件中获取输入并将输出提供给“/code/output.txt”文件,因此出于重定向目的,我使用 proc_open(),这样我就可以轻松重定向流。

我的机器配置:-
操作系统 -> Ubuntu 12.04 LT
PHP 5.3.10
APACHE 2.2.22
在本地主机上运行

权限:-
/代码 -> 777
主php文件所在的文件夹-> 777(我知道777由于安全原因不好,但是因为我在本地服务器上运行脚本,所以我对这些权限没有问题。)

我想要什么:- 谁能告诉我任何方式,以便我可以停止主 php 脚本,直到 proc-open() 创建的进程终止,或者是否有人可以告诉我其他方式来满足我的重定向要求?

这是部分代码:

$descriptors = array(
            2 => array("pipe","w")
        );




        $process = proc_open($cmd,$descriptors,$pipes,$code);

        sleep(2);

        if(is_resource($process))
        {
            $status = proc_get_status($process);
            $pid = $status ["pid"];

            echo getmypid()."<br />";


            echo $pid;

            if(pcntl_waitpid($pid,$child_status) == -1)
            {
                fclose($pipes[2]);
                proc_close($process);
                exit("Sorry!Unable to compile the source_file.\n");


            }


            while(!file_exists("/code/test"))
            {

                proc_close($process);
                echo stream_get_contents($pipes[2]),"\n";
                fclose($pipes[2]);
                exit("Sorry!Unable to compile the source_file.\n");
            }

            echo "Compiled Successfully!\n";
            echo "Running test cases...\n";

            fclose($pipes[2]);

            proc_close($process);

        }
        else
        {
            exit("Sorry!Unable to compile the source_file.\n");
        }

谢谢!

【问题讨论】:

    标签: php


    【解决方案1】:

    这是我的尝试(阅读 stdout 和 stderr(希望)没有死锁):

    class ExecResult {
        public $returnValue;
        public $stdoutBuffer;
        public $stderrBuffer;
    }
    
    class WtfPhpWhyIHaveToDoEverythingMyself {
        public static function exec($proc, $argv) {
            $cwd = getcwd();
            $env = [];
    
            $procAndArgv = count($argv) > 0 ?
                $proc . ' ' . implode(' ', self::escapeArgvs($argv)) :
                $proc;
    
            $pipes = null; // will get filled by proc_open()
    
            $result = new ExecResult();
    
            $processHandle = proc_open(
                $procAndArgv,
                [
                    0 => ['pipe', 'r'], // read/write is from child process's perspective
                    1 => ['pipe', 'w'],
                    2 => ['pipe', 'w']
                ],
                $pipes,
                $cwd,
                $env);
    
            $stdin = $pipes[0];
            $stdout = $pipes[1];
            $stderr = $pipes[2];
    
            fclose($stdin);
    
            stream_set_blocking($stdout, false);
            stream_set_blocking($stderr, false);
    
            $outEof = false;
            $errEof = false;
    
            do {
                $read = [ $stdout, $stderr ]; // [1]
                $write = null; // [1]
                $except = null; // [1]
    
                // [1] need to be as variables because only vars can be passed by reference
    
                stream_select(
                    $read,
                    $write,
                    $except,
                    1, // seconds
                    0); // microseconds
    
                $outEof = $outEof || feof($stdout);
                $errEof = $errEof || feof($stderr);
    
                if (!$outEof) {
                    $result->stdoutBuffer .= fgets($stdout);
                }
    
                if (!$errEof) {
                    $result->stderrBuffer .= fgets($stderr);
                }
            } while(!$outEof || !$errEof);
    
            fclose($stdout);
            fclose($stderr);
    
            $result->returnValue = proc_close($processHandle);
    
            return $result;
        }
    
        private static function escapeArgvs($argv) {
            return array_map(function ($item){
                return escapeshellarg($item);
            }, $argv);
        }
    }
    
    $result = WtfPhpWhyIHaveToDoEverythingMyself::exec('/var/www/html/hello.sh', [
        'arg 1',
        'arg 2',
    ]);
    
    var_dump($result);
    
    /*
    object(ExecResult)#1 (3) {
      ["returnValue"]=> int(0)
      ["stdoutBuffer"]=>
      string(45) "var1 = arg 1
    var2 = arg 2
    "
      ["stderrBuffer"]=>
      string(0) ""
    }
    */
    

    我真的很失望,使用 PHP,您必须自己编写所有管道。例如 node.js 已经用简单的 child_process.spawn() 覆盖了你的背部

    【讨论】:

    • 谢谢!很难找到一个解决方案,不断从流中读取,这样孩子就不会阻塞。大多数代码示例在这方面都被破坏了。
    【解决方案2】:

    最好使用管道来解决这个问题。 pcntl_wait() 仅适用于 Linux,除非您处理 fork(),否则它不是很有用(根据我的经验)。使用 stream_select 等待。如果需要,我会进一步研究我的旧脚本,但它类似于:

    <?php
    
    $cwd = "/code/";
    
    chmod("/code/test.c",0777);
    
    switch($xtension)
    {
        case "c":
            $cmd = "gcc -g test.c -o test -lm ";
        break;
        case "cpp":
            $cmd = "g++ -g test.cpp -o test";
        break;
        case "java":
            $cmd = "javac test.java";
        break;
    }
    
    $descriptors = array(   0 => array('pipe', 'r'), // stdin
                            1 => array('pipe', 'w'), // stdout
                            2 => array('pipe', 'a')  // stderr
    );
    
    $process = proc_open($cmd,$descriptors,$pipes,$code);
    
    stream_set_blocking($pipes[2], 0);
    
    if(($error = stream_get_contents($pipes[2]) !== false)
    {
        // error starting command (error *in* command)
        die($error);
    
    }else
    {
        stream_set_blocking($pipes[2], 1);
    
        /* Prepare the read array */
        $read   = array($pipes[1], $pipes[2]);
        $write  = NULL;
        $except = NULL;
        $tv_sec = 0;      // secs
        $tv_usec = 1000;  // millionths of secs
    
        if(false === ($rv = stream_select($read, $write, $except, $tv_sec, $tv_usec)))
        {
            die('error in stream_select');
    
        }else if ($rv > 0)
        {
            foreach($read as $pipe_resource)
            {
                if($pipe_resource == $pipes[1])
                {
                    // is stdout
    
                }else if($pipe_resource == $pipes[2])
                {
                    // is stderr
                }
            }
    
        }else
        {
            // select timed out. plenty of stuff can be done here
        }
    }
    
        // this will be executed after the process terminates
    

    有很多方法可以用来解决您的问题。

    首先,您可以将 select 中的超时设置为零,它将无限期等待。如果您的可执行文件不使用标准输出,stream_select 函数将阻塞,直到进程终止时管道关闭。

    如果您的进程确实使用了 stdout 和 stderr,您可以在循环中使用 select 并在每次 select 返回时检查管道是否关闭。如果将来需要,这也将允许您有效地处理来自 stdout 和 stderr 的信息(json 非常棒)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-25
      相关资源
      最近更新 更多