2024-07-28 05:00:06

PHP执行后台进程

我需要在用户操作时执行目录复制,但目录相当大,因此我希望能够在用户不知道完成复制所需时间的情况下执行这样的操作。

任何建议都将不胜感激。


当前回答

如果你只需要在后台做一些事情,而不需要PHP页面等待它完成,你可以使用另一个(后台)PHP脚本,用wget命令“调用”。当然,与系统上的任何其他PHP脚本一样,这个后台PHP脚本将以特权执行。

下面是一个在Windows上使用gnuwin32包中的wget的例子。

后台代码(文件test-proc-bg.php)为例…

sleep(5);   // some delay
file_put_contents('test.txt', date('Y-m-d/H:i:s.u')); // writes time in a file

前台脚本,调用…

$proc_command = "wget.exe http://localhost/test-proc-bg.php -q -O - -b";
$proc = popen($proc_command, "r");
pclose($proc);

你必须使用popen/pclose才能正常工作。

wget选项:

-q    keeps wget quiet.
-O -  outputs to stdout.
-b    works on background

其他回答

我只想添加一个非常简单的例子来测试Windows上的这个功能:

创建以下两个文件,并将它们保存到web目录:

foreground.php:

<?php

ini_set("display_errors",1);
error_reporting(E_ALL);

echo "<pre>loading page</pre>";

function run_background_process()
{
    file_put_contents("testprocesses.php","foreground start time = " . time() . "\n");
    echo "<pre>  foreground start time = " . time() . "</pre>";

    // output from the command must be redirected to a file or another output stream 
    // http://ca.php.net/manual/en/function.exec.php

    exec("php background.php > testoutput.php 2>&1 & echo $!", $output);

    echo "<pre>  foreground end time = " . time() . "</pre>";
    file_put_contents("testprocesses.php","foreground end time = " . time() . "\n", FILE_APPEND);
    return $output;
}

echo "<pre>calling run_background_process</pre>";

$output = run_background_process();

echo "<pre>output = "; print_r($output); echo "</pre>";
echo "<pre>end of page</pre>";
?>

background.php:

<?
file_put_contents("testprocesses.php","background start time = " . time() . "\n", FILE_APPEND);
sleep(10);
file_put_contents("testprocesses.php","background end time = " . time() . "\n", FILE_APPEND);
?>

赋予IUSR写入创建上述文件的目录的权限

赋予IUSR读取和执行C:\Windows\System32\cmd.exe的权限

在浏览器中点击前台。php

下面的代码应该在输出数组中使用当前时间戳和本地资源#呈现给浏览器:

loading page
calling run_background_process
  foreground start time = 1266003600
  foreground end time = 1266003600
output = Array
(
    [0] => 15010
)
end of page

您应该看到testoutput.php位于与上面文件保存的相同目录中,并且它应该为空

你应该看到testprocesses.php位于与上面文件相同的目录中,它应该包含以下带有当前时间戳的文本:

foreground start time = 1266003600
foreground end time = 1266003600
background start time = 1266003600
background end time = 1266003610

与其启动后台进程,不如创建一个触发器文件,并让像cron或autosys这样的调度程序定期执行一个寻找触发器文件并对其进行操作的脚本?触发器可以包含指令甚至原始命令(更好的方法是将其变成shell脚本)。

假设这是在Linux机器上运行的,我总是这样处理它:

exec(sprintf("%s > %s 2>&1 & echo $! >> %s", $cmd, $outputfile, $pidfile));

这将启动命令$cmd,将命令输出重定向到$outputfile,并将进程id写入$pidfile。

这使您可以轻松地监视进程正在做什么以及它是否仍在运行。

function isRunning($pid){
    try{
        $result = shell_exec(sprintf("ps %d", $pid));
        if( count(preg_split("/\n/", $result)) > 2){
            return true;
        }
    }catch(Exception $e){}

    return false;
}

使用此函数在后台运行程序。它是跨平台的,完全可定制的。

<?php
function startBackgroundProcess(
    $command,
    $stdin = null,
    $redirectStdout = null,
    $redirectStderr = null,
    $cwd = null,
    $env = null,
    $other_options = null
) {
    $descriptorspec = array(
        1 => is_string($redirectStdout) ? array('file', $redirectStdout, 'w') : array('pipe', 'w'),
        2 => is_string($redirectStderr) ? array('file', $redirectStderr, 'w') : array('pipe', 'w'),
    );
    if (is_string($stdin)) {
        $descriptorspec[0] = array('pipe', 'r');
    }
    $proc = proc_open($command, $descriptorspec, $pipes, $cwd, $env, $other_options);
    if (!is_resource($proc)) {
        throw new \Exception("Failed to start background process by command: $command");
    }
    if (is_string($stdin)) {
        fwrite($pipes[0], $stdin);
        fclose($pipes[0]);
    }
    if (!is_string($redirectStdout)) {
        fclose($pipes[1]);
    }
    if (!is_string($redirectStderr)) {
        fclose($pipes[2]);
    }
    return $proc;
}

注意,在命令启动后,默认情况下,该函数关闭正在运行进程的stdin和stdout。你可以通过$redirectStdout和$redirectStderr参数将进程输出重定向到某个文件中。

windows用户注意: 不能以以下方式将stdout/stderr重定向为null:

startBackgroundProcess('ping yandex.com', null, 'nul', 'nul');

然而,你可以这样做:

startBackgroundProcess('ping yandex.com >nul 2>&1');

*nix用户注意事项:

1)如果你想得到实际的PID,使用exec shell命令:

$proc = startBackgroundProcess('exec ping yandex.com -c 15', null, '/dev/null', '/dev/null');
print_r(proc_get_status($proc));

2)使用$stdin参数,如果你想传递一些数据到你的程序的输入:

startBackgroundProcess('cat > input.txt', "Hello world!\n");

我大量使用fast_cgi_finish_request()

结合使用闭包和register_shutdown_function()

$message ='job executed';
$backgroundJob = function() use ($message) {
     //do some work here
    echo $message;
}

然后注册这个闭包,在关闭之前执行。

register_shutdown_function($backgroundJob);

最后,当响应被发送到客户端时,您可以关闭到客户端的连接,并继续使用PHP进程:

fast_cgi_finish_request();

闭包将在fast_cgi_finish_request之后执行。

$消息在任何时候都不可见。您可以注册任意数量的闭包,但要注意脚本执行时间。 这将只在PHP作为一个快速CGI模块运行时工作(这是正确的吗?!)