php执行后台进程


259

我需要根据用户操作执行目录副本,但是目录很大,因此我希望能够执行这样的操作,而用户却不知道完成副本所花费的时间。

任何建议将不胜感激。


3
这个stackoverflow.com/questions/5367261/…解释了如何在Windows下执行此操作
shealtiel 2011年

Answers:


365

假设它在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;
}

2
sudo安装程序可以在不提示输入密码的情况下运行吗?任何需要用户输入的命令都将不起作用。
Mark Biek 2011年

17
这个stackoverflow.com/questions/5367261/…解释了如何在Windows下执行相同操作
shealtiel 2011年

13
我使用了它,但是稍作更新,因此我不必将PID写入文件。所以我使用这种格式:<code> exec(sprintf(“ $ s> $ s 2>&1&echo $ 1”,$ cmd,$ outputfile),$ pidArr); </ code>生成的进程PID位于$ pidArr [0]
Kaiesh 2013年

3
@Kaiesh:应该是“ echo $!”
brunobg 2013年

8
Kaiesh键入了另一个记号,“ $”而不是“%”。更正后的版本:exec(sprintf(“%s>%s 2>&1&echo $!”,$ cmd,$ outputfile),$ pidArr)
Yuri Gor

23

使用任何方便的语言(php / bash / perl / etc)将过程编写为服务器端脚本,然后从php脚本中的过程控制功能调用它。

该函数可能会检测标准io是否用作输出流,如果是,它将设置返回值。如果没有,则结束

proc_close( proc_open( "./command --foo=1 &", array(), $foo ) );

我使用“ sleep 25s”作为命令从命令行快速进行了测试,它的工作原理很吸引人。

在这里找到答案


3
这是我可以在centos上运行的唯一方法。谢谢!
Deac Karns 2012年

谢谢 !PHP和BASH不兼容。如果您运行较新的系统,它将无法使用system()或exec()在后台启动。即使重定向,文件描述符也似乎卡住了。您的方法有效。另一个替代方法是为PHP使用旧的bash二进制文件,但没关系
John

22

您可能想尝试将此附加到您的命令

>/dev/null 2>/dev/null &

例如。

shell_exec('service named reload >/dev/null 2>/dev/null &');

我的意思是> / dev / null 2> / dev / null&
wlf 2012年

它为我工作,我将使用makefile在服务器站点上执行某些操作,谢谢!(例如make,make restart)
黎楚祥2014年

这比公认的答案要简单得多!(只要您不需要任何更复杂的操作,例如检查进程是否仍在运行。)
Sean the Bean

18

我只想添加一个非常简单的示例以在Windows上测试此功能:

创建以下两个文件并将其保存到Web目录:

前景色.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

从网络浏览器中命中foreground.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

10

如果您只需要在后台执行某些操作而无需等待PHP页面等待其完成,则可以使用wget命令“调用”的另一个(后台)PHP脚本。当然,此后台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

7

这是在PHP中启动后台进程的功能。经过大量阅读并测试了不同的方法和参数后,最终创建了一个实际上也可以在Windows上运行的程序。

function LaunchBackgroundProcess($command){
  // Run command Asynchroniously (in a separate thread)
  if(PHP_OS=='WINNT' || PHP_OS=='WIN32' || PHP_OS=='Windows'){
    // Windows
    $command = 'start "" '. $command;
  } else {
    // Linux/UNIX
    $command = $command .' /dev/null &';
  }
  $handle = popen($command, 'r');
  if($handle!==false){
    pclose($handle);
    return true;
  } else {
    return false;
  }
}

注意1:在Windows上,请勿使用/B其他地方建议的参数。它强制进程运行与start命令本身相同的控制台窗口,从而导致进程被同步处理。要在单独的线程中(异步地)运行进程,请不要使用/B

注意2:start ""如果命令是带引号的路径,则必须在后面加上双引号。start命令将引用的第一个参数解释为窗口标题。


6

好吧,我发现使用起来更快更简单

shell_exec('screen -dmS $name_of_screen $command'); 

而且有效。


@ Alpha.Dev我只是在Linux上测试过,我不知道它是否可以在另一个系统上运行。
Morfidon

4

您是否可以安排一个单独的过程,然后在后台运行副本?自从我编写了PHP已经有一段时间了,但是功能pcntl-fork看起来很有希望。


这不能为问题提供答案。要批评或要求作者澄清,请在其帖子下方发表评论。
Rizier123

13
谢谢-评论在'08中不存在,但我会记住这一点:)
Matt Sheppard 2015年

4

使用此功能可以在后台运行程序。它跨平台且完全可定制。

<?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用户注意事项:
不能以nul以下方式将stdout / stderr重定向到:

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");

3

您可以尝试像Resque这样的排队系统。然后,您可以生成一个作业,该作业处理信息并通过“处理中”图像快速返回。使用这种方法,您将不会知道何时完成。

此解决方案适用于大型应用程序,在这些应用程序中您不希望前端机器执行繁重的工作,因此它们可以处理用户请求。因此,它可能会或可能不会与文件和文件夹之类的物理数据一起使用,但是对于处理更复杂的逻辑或其他异步任务(即新的注册邮件),它具有很好的扩展性。


2

适用于Windows和Linux的有效解决方案。在我的github页面上找到更多信息。

function run_process($cmd,$outputFile = '/dev/null', $append = false){
                    $pid=0;
                if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {//'This is a server using Windows!';
                        $cmd = 'wmic process call create "'.$cmd.'" | find "ProcessId"';
                        $handle = popen("start /B ". $cmd, "r");
                        $read = fread($handle, 200); //Read the output 
                        $pid=substr($read,strpos($read,'=')+1);
                        $pid=substr($pid,0,strpos($pid,';') );
                        $pid = (int)$pid;
                        pclose($handle); //Close
                }else{
                    $pid = (int)shell_exec(sprintf('%s %s %s 2>&1 & echo $!', $cmd, ($append) ? '>>' : '>', $outputFile));
                }
                    return $pid;
            }
            function is_process_running($pid){
                if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {//'This is a server using Windows!';
                        //tasklist /FI "PID eq 6480"
                    $result = shell_exec('tasklist /FI "PID eq '.$pid.'"' );
                    if (count(preg_split("/\n/", $result)) > 0 && !preg_match('/No tasks/', $result)) {
                        return true;
                    }
                }else{
                    $result = shell_exec(sprintf('ps %d 2>&1', $pid));
                    if (count(preg_split("/\n/", $result)) > 2 && !preg_match('/ERROR: Process ID out of range/', $result)) {
                        return true;
                    }
                }
                return false;
            }
            function stop_process($pid){
                    if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {//'This is a server using Windows!';
                            $result = shell_exec('taskkill /PID '.$pid );
                        if (count(preg_split("/\n/", $result)) > 0 && !preg_match('/No tasks/', $result)) {
                            return true;
                        }
                    }else{
                            $result = shell_exec(sprintf('kill %d 2>&1', $pid));
                        if (!preg_match('/No such process/', $result)) {
                            return true;
                        }
                    }
            }


1

与其启动后台进程,不如创建触发文件并让cron或autosys之类的调度程序定期执行查找触发文件并对其起作用的脚本,该怎么办?触发器可以包含指令,甚至可以包含原始命令(更好的是,仅使它成为Shell脚本)。



1

我正在大量使用 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模块运行时才有效(是吗?!)


0

PHP脚本与其他桌面应用程序开发语言不同。在桌面应用程序语言中,我们可以将守护程序线程设置为运行后台进程,但是在PHP中,当用户请求页面时,进程正在发生。但是,可以使用运行PHP脚本的服务器的Cron作业功能来设置后台作业。


0

对于那些使用Windows的人,请看以下内容:

参考:http : //php.net/manual/zh/function.exec.php#43917

当脚本继续执行时,我也想让程序在Windows中在后台运行。与其他解决方案不同,此方法使您可以启动最小化,最大化或根本没有窗口的任何程序。llbra @ phpbrasil的解决方案确实有效,但是当您确实希望任务隐藏运行时,有时会在桌面上生成一个不需要的窗口。

在后台最小化启动Notepad.exe:

<?php 
$WshShell = new COM("WScript.Shell"); 
$oExec = $WshShell->Run("notepad.exe", 7, false); 
?> 

启动一个在后台不可见的shell命令:

<?php 
$WshShell = new COM("WScript.Shell"); 
$oExec = $WshShell->Run("cmd /C dir /S %windir%", 0, false); 
?> 

启动MSPaint最大化并等待您关闭它,然后继续执行脚本:

<?php 
$WshShell = new COM("WScript.Shell"); 
$oExec = $WshShell->Run("mspaint.exe", 3, true); 
?> 

有关Run()方法的更多信息,请访问:http : //msdn.microsoft.com/library/en-us/script56/html/wsMthRun.asp

编辑的网址:

请转到https://technet.microsoft.com/zh-cn/library/ee156605.aspx,因为上面的链接不再存在。


1
要运行此代码需要做什么?我知道了Fatal error: Class 'COM' not found
Alph.Dev

ps到MSDN的链接已损坏
Alph.Dev '16

@ Alph.Dev:msdn文档似乎已被临时删除。它包含有关如何使用WScript.Shell的更多示例和说明。
吉米·伊伦洛亚

@ Alph.Dev:对于未找到的COM类,这完全是另一回事。在Google上查看。您可能还会检查此php.net/manual/en/com.installation.php
Jimmy Ilenloa,2016年

@ Alph.Dev请检查technet.microsoft.com/zh-cn/library/ee156605.aspx,以获取有关Run()函数的更新信息
Jimmy Ilenloa,

-12

我知道这是一个已有100年历史的职位,但是无论如何,我认为这可能对某人有用。您可以在页面上的某处放置一个不可见的图像,以指向需要在后台运行的url,如下所示:

<img src="run-in-background.php" border="0" alt="" width="1" height="1" />


12
真的很糟糕的解决方案。如果用户离开页面,则过程将被中断。
谢尔盖(Sergey P.)又名天青

3
页面源代码中公开了“隐形图像”及其链接。
tony gil 2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.