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有更简单的方法:
我正在使用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" />