PHP执行后台进程

我需要在用户操作时执行一个目录副本,但目录非常大,所以我希望能够执行这样的操作,而用户不需要知道拷贝完成所需的时间。

任何build议将不胜感激。

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

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

这将启动命令$cmd ,将命令输出redirect到$outputfile ,并将进程标识写入$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 / bash / perl / etc)方便地将进程编写成服务器端脚本,然后从你的php脚本中的进程控制函数中调用它。

该函数可能检测标准IO是否被用作输出stream,如果是,那么将设​​置返回值..如果不是,那么它结束

 Proc_Close (Proc_Open ("./command --foo=1 &", Array (), $foo)); 

我使用“sleep 25s”作为命令,从命令行快速testing了这个命令,它的function就像一个魅力一样。

( 在这里find答案 )

我只想添加一个非常简单的例子来在Windows上testing这个function:

创build以下两个文件并将其保存到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权限以写入您创build上述文件的目录

授予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,它应该包含下面的文本w /当前的时间戳:

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

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

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

例如。

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

如果您只需要在后台执行某些操作,而无需等待PHP页面完成,则可以使用另一个(后台)PHP脚本,该脚本通过wget命令“调用”。 这个背景PHP脚本将被执行,当然还有系统上的任何其他PHP脚本。

这里是一个使用gnuwin32包中的wget的例子。

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

 sleep(5); // some delay file_put_contents('test.txt', date('Ymd/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 

那么我发现一个更快,更简单的版本使用

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

它的工作。

你可以尝试像Resque这样的排队系统。 然后,您可以生成一份工作,处理信息,并通过“处理”图像快速返回。 用这种方法你不会知道什么时候完成。

此解决scheme适用于较大规模的应用程序,您不希望前端机器繁重工作,以便处理用户请求。 因此,它可能会或可能不会与文件和文件夹等物理数据一起工作,但是为了处理更复杂的逻辑或其他asynchronous任务(即新的注册邮件),具有非常好的可伸缩性是很好的。

这是一个在PHP中启动后台进程的函数。 最后创build一个实际上在Windows上工作,经过大量的阅读和testing不同的方法和参数。

 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上,不要使用其他地方build议的/B参数。 它强制进程与start命令本身运行相同的控制台窗口,导致进程被同步处理。 要在单独的线程(asynchronous)中运行进程,请不要使用/B

注2:如果命令是带引号的path,则在start ""之后的空双引号是必需的。 start命令将第一个引用的参数解释为窗口标题。

你可以安排一个单独的进程,然后在后台运行你的副本? 自从我做了任何PHP之后已经有一段时间了,但pcntl-fork函数看起来很有前途。

而不是启动一个后台进程,创build一个触发器文件和像cron或autosys这样的调度器会周期性地执行一个脚本来查找和执行触发器文件? 触发器可能包含指令甚至原始的命令(更好的是,只是把它作为一个shell脚本)。

如果使用PHP,使用pcntl_fork有更简单的方法:

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

我正在使用fast_cgi_finish_request()

结合闭包和register_shutdown_function()

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

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

 register_shutdown_function($backgroundJob); 

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

 fast_cgi_finish_request(); 

闭包将在fast_cgi_finish_request之后执行。

$消息在任何时候都不可见。 您可以根据需要注册尽可能多的closures,但要注意脚本执行时间。 这只会在PHP作为一个Fast CGI模块运行时才起作用(对吗?!)

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

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

请注意,命令启动后,默认情况下该函数closures正在运行的进程的标准input和标准输出。 您可以通过$ redirectStdout和$ redirectStderr参数将stream程输出redirect到某个文件。

注意Windows用户:没有办法将stdout / stderrredirect到nul设备。 不幸的是,你不能这样做:

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

所以你应该将stderr / stdinredirect到有效的文件中,否则你的命令应该是没有stderr / stdout描述符。

* 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)如果你想传递一些数据到程序的input,使用$ stdin参数:

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

PHP脚本不像其他桌面应用程序开发语言。 在桌面应用程序语言中,我们可以设置守护线程来运行后台进程,但是在PHP中,当用户请求页面时会发生一个进程。 但是,可以使用php脚本运行的服务器的cron作业function来设置后台作业。

对于我们这些使用Windows的人来说 ,看看这个:

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

我也摔跤得到一个程序在Windows的后台运行,而脚本继续执行。 与其他解决scheme不同,此方法允许您启动任何最小化,最大化或无窗口的程序。 llbra @ phpbrasil的解决scheme可以工作,但是当你真的想让任务运行隐藏时,它有时会在桌面上产生一个不需要的窗口。

启动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最大化并等待您closures它,然后继续脚本:

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

Windows和Linux的工作解决scheme。 在我的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; } } } 

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

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