简体   繁体   English

php 执行后台进程

[英]php execute a background process

I need to execute a directory copy upon a user action, but the directories are quite large, so I would like to be able to perform such an action without the user being aware of the time it takes for the copy to complete.我需要在用户操作时执行目录复制,但是目录非常大,所以我希望能够在用户不知道复制完成所需时间的情况下执行这样的操作。

Any suggestions would be much appreciated.任何建议将不胜感激。

Assuming this is running on a Linux machine, I've always handled it like this:假设这是在 Linux 机器上运行,我总是这样处理它:

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

This launches the command $cmd , redirects the command output to $outputfile , and writes the process id to $pidfile .这将启动命令$cmd ,将命令输出重定向到$outputfile ,并将进程 ID 写入$pidfile

That lets you easily monitor what the process is doing and if it's still running.这让您可以轻松监控进程正在做什么以及它是否仍在运行。

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;
}

Write the process as a server-side script in whatever language (php/bash/perl/etc) is handy and then call it from the process control functions in your php script.使用任何方便的语言(php/bash/perl/etc)将流程编写为服务器端脚本,然后从您的 php 脚本中的流程控制函数中调用它。

The function probably detects if standard io is used as the output stream and if it is then that will set the return value..if not then it ends该函数可能会检测标准 io 是否用作输出流,如果是,则将设置返回值..如果不是,则结束

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

I tested this quickly from the command line using "sleep 25s" as the command and it worked like a charm.我使用“sleep 25s”作为命令从命令行快速测试了这一点,它的效果非常好。

( Answer found here ) 答案在这里找到

You might want to try to append this to your command您可能想尝试将此附加到您的命令

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

eg.例如。

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

I'd just like to add a very simple example for testing this functionality on Windows:我只想添加一个非常简单的示例来在 Windows 上测试此功能:

Create the following two files and save them to a web directory:创建以下两个文件并将它们保存到 Web 目录:

foreground.php:前台.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:背景.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);
?>

Give IUSR permission to write to the directory in which you created the above files授予 IUSR 写入您创建上述文件的目录的权限

Give IUSR permission to READ and EXECUTE C:\\Windows\\System32\\cmd.exe授予IUSR READ 和EXECUTE C:\\Windows\\System32\\cmd.exe 权限

Hit foreground.php from a web browser从 Web 浏览器点击 foreground.php

The following should be rendered to the browser w/the current timestamps and local resource # in the output array:以下应在输出数组中使用当前时间戳和本地资源 # 呈现给浏览器:

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

You should see testoutput.php in the same directory as the above files were saved, and it should be empty您应该在保存上述文件的同一目录中看到 testoutput.php ,它应该是空的

You should see testprocesses.php in the same directory as the above files were saved, and it should contain the following text w/the current timestamps:您应该在保存上述文件的同一目录中看到 testprocesses.php,它应该包含以下文本和当前时间戳:

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

If you need to just do something in background without the PHP page waiting for it to complete, you could use another (background) PHP script that is "invoked" with wget command.如果您只需要在后台执行某些操作,而无需等待 PHP 页面完成,您可以使用另一个(后台)PHP 脚本,该脚本通过 wget 命令“调用”。 This background PHP script will be executed with privileges, of course, as any other PHP script on your system.当然,这个后台 PHP 脚本将以特权执行,就像您系统上的任何其他 PHP 脚本一样。

Here is an example on Windows using wget from gnuwin32 packages.这是在 Windows 上使用 gnuwin32 包中的 wget 的示例。

The background code (file test-proc-bg.php) as an exmple ...以后台代码(文件 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

The foreground script, the one invoking ...前台脚本,调用...

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

You must use the popen/pclose for this to work properly.您必须使用 popen/pclose 才能正常工作。

The wget options: wget 选项:

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

Well i found a bit faster and easier version to use好吧,我发现了一个更快更容易使用的版本

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

and it works.它有效。

Here is a function to launch a background process in PHP.这是一个在 PHP 中启动后台进程的函数。 Finally created one that actually works on Windows too, after a lot of reading and testing different approaches and parameters.经过大量阅读和测试不同的方法和参数,最终创建了一个也可以在 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;
  }
}

Note 1: On windows, do not use /B parameter as suggested elsewhere.注意 1:在 Windows 上,不要像其他地方建议的那样使用/B参数。 It forces process to run the same console window as start command itself, resulting in the process being processed synchronously.它强制进程运行与start命令本身相同的控制台窗口,从而同步处理进程。 To run the process in a separate thread (asynchronously), do not use /B .要在单独的线程(异步)中运行该进程,请不要使用/B

Note 2: The empty double quotes after start "" are required if the command is a quoted path.注 2:如果命令是带引号的路径,则start ""后的空双引号是必需的。 start command interprets the first quoted parameter as window title. start命令将第一个带引号的参数解释为窗口标题。

Use this function to run your program in background.使用此功能在后台运行您的程序。 It cross-platform and fully customizable.它跨平台且完全可定制。

<?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;
}

Note that after command started, by default this function closes the stdin and stdout of running process.请注意,命令启动后,默认情况下此函数会关闭正在运行的进程的 stdin 和 stdout。 You can redirect process output into some file via $redirectStdout and $redirectStderr arguments.您可以通过 $redirectStdout 和 $redirectStderr 参数将进程输出重定向到某个文件中。

Note for windows users: windows用户注意事项:
You cannot redirect stdout/stderr to nul in the following manner:您不能通过以下方式将 stdout/stderr 重定向到nul

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

However, you can do this:但是,您可以这样做:

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

Notes for *nix users: *nix 用户注意事项:

1) Use exec shell command if you want get actual PID: 1) 如果你想获得实际的 PID,请使用 exec shell 命令:

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

2) Use $stdin argument if you want to pass some data to the input of your program: 2) 如果要将一些数据传递给程序的输入,请使用 $stdin 参数:

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

Can you arrange to fork off a separate process, and then run your copy in the background?你能安排一个单独的进程,然后在后台运行你的副本吗? It's been a while since I did any PHP, but the function pcntl-fork looks promising.我已经有一段时间没有使用任何 PHP 了,但是pcntl-fork函数看起来很有希望。

You might try a queuing system like Resque .您可以尝试使用Resque这样的排队系统。 You then can generate a job, that processes the information and quite fast return with the "processing" image.然后,您可以生成一个作业,该作业处理信息并通过“处理”图像快速返回。 With this approach you won't know when it is finished though.使用这种方法,您将不知道何时完成。

This solution is intended for larger scale applications, where you don't want your front machines to do the heavy lifting, so they can process user requests.此解决方案适用于更大规模的应用程序,您不希望前端机器承担繁重的工作,因此它们可以处理用户请求。 Therefore it might or might not work with physical data like files and folders, but for processing more complicated logic or other asynchronous tasks (ie new registrations mails) it is nice to have and very scalable.因此,它可能会或可能不会处理物理数据,如文件和文件夹,但对于处理更复杂的逻辑或其他异步任务(即新注册邮件),它很好并且非常可扩展。

A working solution for both Windows and Linux.适用于 Windows 和 Linux 的工作解决方案。 Find more on My github page .我的 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;
                        }
                    }
            }

Thanks to this answer : A perfect tool to run a background process would be Symfony Process Component , which is based on proc_* functions, but it's much easier to use.感谢这个答案:运行后台进程的完美工具是Symfony Process Component ,它基于proc_*函数,但它更容易使用。 See its documentation for more information.有关更多信息,请参阅其文档

Instead of initiating a background process, what about creating a trigger file and having a scheduler like cron or autosys periodically execute a script that looks for and acts on the trigger files?与其启动后台进程,不如创建一个触发器文件并让诸如 cron 或 autosys 之类的调度程序定期执行一个脚本来查找触发器文件并对其进行操作? The triggers could contain instructions or even raw commands (better yet, just make it a shell script).触发器可以包含指令甚至原始命令(更好的是,只需将其设为 shell 脚本)。

If using PHP there is a much easier way to do this using pcntl_fork:如果使用 PHP,则使用 pcntl_fork 有更简单的方法:

http://www.php.net/manual/en/function.pcntl-fork.php http://www.php.net/manual/en/function.pcntl-fork.php

I am heavily using fast_cgi_finish_request()我大量使用fast_cgi_finish_request()

In combination with a closure and register_shutdown_function()结合闭包和 register_shutdown_function()

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

Then register this closure to be executed before shutdown.然后注册这个闭包在关机前执行。

register_shutdown_function($backgroundJob);

Finally when the response was sent to the client you can close the connection to the client and continue working with the PHP process:最后,当响应发送到客户端时,您可以关闭与客户端的连接并继续使用 PHP 进程:

fast_cgi_finish_request();

The closure will be executed after fast_cgi_finish_request.关闭将在 fast_cgi_finish_request 之后执行。

The $message will not be visible at any time. $message 在任何时候都不可见。 And you can register as much closures as you want, but take care about script execution time.您可以根据需要注册尽可能多的闭包,但要注意脚本执行时间。 This will only work if PHP is running as a Fast CGI module (was that right?!)这仅在 PHP 作为 Fast CGI 模块运行时才有效(对吗?!)

If you are looking to execute a background process via PHP, pipe the command's output to /dev/null and add & to the end of the command.如果您希望通过 PHP 执行后台进程,请将命令的输出通过管道传送到/dev/null并将&添加到命令的末尾。

exec("bg_process > /dev/null &");

Note that you can not utilize the $output parameter of exec() or else PHP will hang (probably until the process completes).请注意,您不能使用exec()$output参数,否则 PHP 将挂起(可能直到进程完成)。

PHP scripting is not like other desktop application developing language. PHP 脚本与其他桌面应用程序开发语言不同。 In desktop application languages we can set daemon threads to run a background process but in PHP a process is occuring when user request for a page.在桌面应用程序语言中,我们可以设置守护线程来运行后台进程,但在 PHP 中,当用户请求页面时,进程正在发生。 However It is possible to set a background job using server's cron job functionality which php script runs.但是,可以使用 php 脚本运行的服务器的 cron 作业功能设置后台作业。

For those of us using Windows , look at this:对于我们这些使用Windows 的人,看看这个:

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

I too wrestled with getting a program to run in the background in Windows while the script continues to execute.我也在努力让程序在 Windows 的后台运行,同时脚本继续执行。 This method unlike the other solutions allows you to start any program minimized, maximized, or with no window at all.与其他解决方案不同,此方法允许您启动任何最小化、最大化或根本没有窗口的程序。 llbra@phpbrasil's solution does work but it sometimes produces an unwanted window on the desktop when you really want the task to run hidden. llbra@phpbrasil 的解决方案确实有效,但是当您确实希望任务隐藏运行时,它有时会在桌面上产生一个不需要的窗口。

start Notepad.exe minimized in the background:在后台启动最小化的 Notepad.exe:

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

start a shell command invisible in the background:在后台启动一个不可见的 shell 命令:

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

start MSPaint maximized and wait for you to close it before continuing the script:启动 MSPaint 最大化并等待您将其关闭,然后再继续执行脚本:

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

For more info on the Run() method go to: http://msdn.microsoft.com/library/en-us/script56/html/wsMthRun.asp有关 Run() 方法的更多信息,请访问: http : //msdn.microsoft.com/library/en-us/script56/html/wsMthRun.asp

Edited URL:修改后的网址:

Go to https://technet.microsoft.com/en-us/library/ee156605.aspx instead as the link above no longer exists.请转至https://technet.microsoft.com/en-us/library/ee156605.aspx,因为上面的链接不再存在。

New answer to an old question.老问题的新答案。 Using this library , the following code would spawn an asynchronous/parallel PHPThread to do background work.使用这个库,下面的代码会生成一个异步/并行的 PHPThread 来做后台工作。

  • Must have pcntl, posix, and socket extensions必须有 pcntl、posix 和 socket 扩展
  • Designed for/tested in CLI mode.为 CLI 模式设计/测试。

EZ code sample: EZ 代码示例:

 function threadproc($thread, $param) {
 
    echo "\tI'm a PHPThread.  In this example, I was given only one parameter: \"". print_r($param, true) ."\" to work with, but I can accept as many as you'd like!\n";
 
    for ($i = 0; $i < 10; $i++) {
        usleep(1000000);
        echo "\tPHPThread working, very busy...\n";
    }
 
    return "I'm a return value!";
}
 

$thread_id = phpthread_create($thread, array(), "threadproc", null, array("123456"));
 
echo "I'm the main thread doing very important work!\n";
 
for ($n = 0; $n < 5; $n++) {
    usleep(1000000);
    echo "Main thread...working!\n";
}
 
echo "\nMain thread done working.  Waiting on our PHPThread...\n";
 
phpthread_join($thread_id, $retval);
 
echo "\n\nOur PHPThread returned: " . print_r($retval, true) . "!\n";

From PHP official documentation(php.net)来自 PHP 官方文档(php.net)

<?php
function execInBackground($cmd) {
    if (substr(php_uname(), 0, 7) == "Windows"){
        pclose(popen("start /B ". $cmd, "r")); 
    }
    else {
        exec($cmd . " > /dev/null &");  
    }
}
?>

I know it is a 100 year old post, but anyway, thought it might be useful to someone.我知道这是一个 100 岁的帖子,但无论如何,认为它可能对某人有用。 You can put an invisible image somewhere on the page pointing to the url that needs to run in the background, like this:您可以在页面上的某处放置一个不可见的图像,指向需要在后台运行的 url,如下所示:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM