양식을 제출 한 후 백그라운드에서 PHP 스크립트를 어떻게 실행할 수 있습니까?


104

문제
제출시 기본 코드를 실행하여 제출 된 정보를 처리하고 알림 웹 사이트에 표시하기 위해 데이터베이스에 삽입하는 양식이 있습니다. 또한 이메일 및 SMS 메시지를 통해 이러한 알림을 받도록 등록한 사람들의 목록이 있습니다. 이 목록은 현재로서는 사소한 일이지만 (약 150 명만 입력) 전체 구독자 테이블을 순환하고 150 개 이상의 이메일을 발송하는 데 1 분 이상 걸리기 만하면됩니다. (이메일은 대량 이메일 정책으로 인해 이메일 서버의 시스템 관리자가 요청한대로 개별적으로 전송됩니다.)

이 시간 동안 알림을 게시 한 개인은 알림이 게시되고 있다는 긍정적 인 보강없이 거의 1 분 동안 양식의 마지막 페이지에 앉아있을 것입니다. 이것은 내가 이상적이지 않다고 생각하는 가능한 해결책을 가진 다른 잠재적 인 문제로 이어집니다.

  1. 첫째, 포스터는 서버가 지연되고 있다고 생각하고 '제출'버튼을 다시 클릭하면 스크립트가 다시 시작되거나 두 번 실행됩니다. JavaScript를 사용하여 버튼을 비활성화하고 텍스트를 'Processing ...'과 같은 텍스트로 바꾸면이 문제를 해결할 수 있지만 사용자가 스크립트 실행 시간 동안 페이지에 계속 머물러 있기 때문에 이상적이지 않습니다. (또한 JavaScript가 비활성화 된 경우에도이 문제가 여전히 존재합니다.)

  2. 둘째, 포스터는 양식을 제출 한 후 탭이나 브라우저를 너무 일찍 닫을 수 있습니다. 스크립트는 브라우저에 다시 쓰기를 시도 할 때까지 서버에서 계속 실행되지만 사용자가 도메인 내의 페이지를 탐색하면 (스크립트가 아직 실행중인 동안) 브라우저는 스크립트가 종료 될 때까지 페이지로드를 중단합니다. . (이것은 브라우저의 탭이나 창이 전체 브라우저 응용 프로그램이 아닌 닫혀있을 때만 발생합니다.) 그래도 이상적이지 않습니다.

(가능) 해결 방법
스크립트의 "이메일"부분을 알림이 게시 된 후 호출 할 수있는 별도의 파일로 나누고 싶습니다. 처음에는 알림이 성공적으로 게시 된 후 확인 페이지에 게시 할 생각이었습니다. 그러나 사용자는이 스크립트가 실행되고 있다는 것을 알지 못하며 어떤 이상 현상도 눈에 띄지 않습니다. 이 스크립트는 실패 할 수 없습니다.

하지만이 스크립트를 백그라운드 프로세스로 실행할 수 있다면 어떨까요? 제 질문은 이것이다 : PHP 스크립트를 어떻게 실행하여 백그라운드 서비스로 트리거하고 사용자가 양식 수준에서 수행 한 작업과 완전히 독립적으로 실행할 수 있습니까?

편집 : 이것은 cron'ed 수 없습니다 . 양식이 제출되는 즉시 실행되어야합니다. 우선 순위가 높은 알림입니다. 또한 서버를 실행하는 시스템 관리자는 크론이 5 분 이상 자주 실행되는 것을 허용하지 않습니다.


크론 작업을 시작하고 다른 페이지에서 사용자에게 요청이 처리되고 있음을 알립니다.
Evan Mulawski 2011 년

1
정기적으로 (매일 또는 매시간) 실행 하시겠습니까, 아니면 제출할 때 실행 하시겠습니까? 나는 당신이 PHP를 사용할 수 있다고 생각 exec()하지만 이것을 시도한 적이 없습니다.
사이먼

2
배경 프로세스를 실행하기 위해 루프 내부에 시간 간격을 사용 ajax하고 삽입 하기 만하면됩니다 sleep()(제 경우에는 foreach 사용). 내 서버에서 완벽하게 작동합니다. 다른 사람들에 대해서는 확실하지 않습니다. 또한 루프 앞에 ignore_user_abort()set_time_limit()(계산 된 종료 시간 포함)을 추가 하여 스크립트가 내 테스트에 따라 ignore_user_abort()및 없이 작업을 완료하는 스크립트를 사용하는 서버에 의해 중지되지 않도록합니다 set_time_limit().
아리

나는 당신이 이미 해결책을 제시하는 것을 봤습니다. 나는 gearman백그라운드 작업을 처리하기 위해 PHP와 함께 사용 합니다. 처리 시간이 많고로드가 많은 경우 확장하는 것이 완벽합니다. 당신은 그것을 조사해야합니다.
Andre Garcia

[유래]에이 답변에 따라 ( stackoverflow.com/a/46617107/6417678 )
Joshy 프란시스

답변:


89

몇 가지 실험을 수행 exec하고 shell_exec난 완벽하게 작동하는 솔루션을 발견했다! shell_exec발생하거나 발생하지 않는 모든 알림 프로세스를 기록 할 수 있도록 사용 하기로 선택합니다 . ( shell_exec문자열로 반환되며 exec이것은를 사용 하는 것보다 쉬웠으며 출력을 변수에 할당 한 다음 쓸 파일을 여는 것보다 쉽습니다 .)

다음 줄을 사용하여 이메일 스크립트를 호출하고 있습니다.

shell_exec("/path/to/php /path/to/send_notifications.php '".$post_id."' 'alert' >> /path/to/alert_log/paging.log &");

&@netcoder가 지적한대로 명령의 끝에있는를 확인 하는 것이 중요합니다 . 이 UNIX 명령은 백그라운드에서 프로세스를 실행합니다.

스크립트 경로 뒤에 작은 따옴표로 묶인 추가 변수는 스크립트 $_SERVER['argv']내에서 호출 할 수있는 변수 로 설정됩니다 .

그런 다음 이메일 스크립트는를 사용하여 내 로그 파일에 >>출력하고 다음과 같이 출력합니다.

[2011-01-07 11:01:26] Alert Notifications Sent for http://alerts.illinoisstate.edu/2049 (SCRIPT: 38.71 seconds)
[2011-01-07 11:01:34] CRITICAL ERROR: Alert Notifications NOT sent for http://alerts.illinoisstate.edu/2049 (SCRIPT: 23.12 seconds)

감사. 이것은 내 생명을 구했습니다.
Liam Bailey

$post_idPHP 스크립트의 경로 뒤에는 무엇입니까 ?
Perocat 2014

@Perocat은 스크립트로 보내는 변수입니다. 이 경우 호출되는 스크립트에 매개 변수가 될 ID를 전송합니다.
Chique 2014 년

나는이 완벽하게 작업을 수행 의심 참조 stackoverflow.com/questions/2212635/...
symcbean

1
탈출하기 위해 보류중인 수정 제안이 있습니다 $post_id. 차라리 숫자로 직접 캐스트하고 싶습니다 (int) $post_id. (어떤 것이 더 나은지 결정하기 위해주의를 기울이십시오.)
Al.G.

19

Linux / Unix 서버에서는 proc_open 을 사용하여 백그라운드에서 작업을 실행할 수 있습니다 .

$descriptorspec = array(
   array('pipe', 'r'),               // stdin
   array('file', 'myfile.txt', 'a'), // stdout
   array('pipe', 'w'),               // stderr
);

$proc = proc_open('php email_script.php &', $descriptorspec, $pipes);

여기서 &중요한 부분입니다. 원본 스크립트가 종료 된 경우에도 스크립트는 계속됩니다.


이것은 proc_close작업이 완료되었을 때 전화하지 않을 때 작동합니다 . proc_close스크립트를 약 15 ~ 25 초 동안 정지시킵니다. 파이프 정보를 사용하여 로그를 보관해야하므로 쓸 파일을 열고 닫은 다음 proc 연결을 닫아야합니다. 따라서 불행히도이 솔루션은 저에게 적합하지 않습니다.
Michael Irigoyen 2011 년

3
물론 전화하지 마세요 proc_close, 그게 요점입니다! 그리고 예, proc_open. 업데이트 된 답변을 참조하십시오.
netcoder 2011 년

@netcoder : 결과를 파일에 저장하지 않으려면 어떻게해야합니까? stdout에서 어떤 변경이 필요합니까?
naggarwal11 2015

9

모든 답변 중에서 누구도 호출시 남아있는 모든 출력을 브라우저에 플러시하고 Fastcgi 세션과 HTTP 연결을 닫으면서 스크립트가 백그라운드에서 실행되도록 하는 말도 안되게 쉬운 fastcgi_finish_request 함수를 고려하지 않았습니다 .

예 :

<?php
header('Content-Type: application/json');
echo json_encode(['ok' => true]);
fastcgi_finish_request(); // The user is now disconnected from the script

// do stuff with received data,

이것이 바로 내가 찾던 것입니다. shell_exec에서 게시 된 데이터를 실행 된 스크립트로 전송해야합니다. 실행 된 스크립트는 제 경우에는 시간이 많이 걸리고 상황을 매우 복잡하게 만듭니다.
Aurangzeb

7

PHP가 exec("php script.php")할 수 있습니다.

로부터 수동 :

프로그램이이 함수로 시작된 경우 백그라운드에서 계속 실행하려면 프로그램의 출력을 파일 또는 다른 출력 스트림으로 리디렉션해야합니다. 그렇게하지 않으면 프로그램 실행이 끝날 때까지 PHP가 중단됩니다.

따라서 출력을 로그 파일로 리디렉션하면 (어쨌든 좋은 아이디어) 호출 스크립트가 중단되지 않고 이메일 스크립트가 bg에서 실행됩니다.


1
감사합니다.하지만 작동하지 않았습니다. 두 번째 파일을 처리하는 동안 스크립트는 여전히 "중단"됩니다.
Michael Irigoyen 2011 년

php.net/manual/en/function.exec.php#80582 명령을 보십시오 . Safe_mode가 활성화되어 있기 때문입니다. 유닉스에는 해결 방법이 있습니다.
Simon

안타깝게도 설치시 안전 모드가 활성화되어 있지 않습니다. 여전히 매달려있는 것이 이상합니다.
Michael Irigoyen 2011 년

6

그리고 스크립트에서 HTTP 요청을 만들고 응답을 무시하지 않는 이유는 무엇입니까?

http://php.net/manual/en/function.httprequest-send.php

스크립트에 대한 요청을하는 경우 웹 서버를 호출해야 백그라운드에서 실행되며 (메인 스크립트에서) 스크립트가 실행 중임을 사용자에게 알리는 메시지를 표시 할 수 있습니다.


4

이건 어때요?

  1. 양식을 보유하는 PHP 스크립트는 플래그 또는 일부 값을 데이터베이스 또는 파일에 저장합니다.
  2. 두 번째 PHP 스크립트는이 값을 주기적으로 폴링하며, 설정된 경우 동기 방식으로 이메일 스크립트를 트리거합니다.

이 두 번째 PHP 스크립트는 cron으로 실행되도록 설정해야합니다.


감사합니다. 원래 질문을 업데이트했습니다. 이 경우 Cron은 옵션이 아닙니다.
Michael Irigoyen 2011 년

4

쉬운 방법으로 이것을 할 수 없다는 것을 알고 있기 때문에 (fork exec 등 (창에서 작동하지 않음) 참조) 접근 방식을 되돌릴 수 있으며 브라우저의 배경을 사용하여 ajax로 양식을 게시하므로 게시물이 여전히 당신은 대기 시간이 없습니다.
이것은 긴 정교함을해야하는 경우에도 도움이 될 수 있습니다.

메일 보내기에 관해서는 항상 스풀러를 사용하는 것이 좋습니다. 큐를 스풀링하는 크론을 사용하는 것보다 요청을 수락하고이를 실제 MTA에 스풀링하거나 모두 DB에 넣는 로컬 및 빠른 smtp 서버 일 수 있습니다.
크론은 스풀러를 외부 URL로 호출하는 다른 시스템에있을 수 있습니다.

* * * * * wget -O /dev/null http://www.example.com/spooler.php

3

백그라운드 크론 작업은 이에 대한 좋은 생각처럼 들립니다.

스크립트를 cron으로 실행하려면 머신에 대한 ssh 액세스 권한이 필요합니다.

$ php scriptname.php 그것을 실행합니다.


1
ssh가 필요 없습니다. 많은 호스트가 ssh를 허용하지 않지만 cron 지원 기능이 있습니다.
Teson 2011 년

감사합니다. 원래 질문을 업데이트했습니다. 이 경우 Cron은 옵션이 아닙니다.
Michael Irigoyen 2011 년

3

ssh를 통해 서버에 액세스 할 수 있고 자신의 스크립트를 실행할 수 있다면 php를 사용하여 간단한 fifo 서버를 만들 수 있습니다 (하지만을 posix지원하여 php를 다시 컴파일해야합니다 fork).

서버는 실제로 무엇이든 쓸 수 있으며 아마도 파이썬으로 쉽게 할 수 있습니다.

또는 가장 간단한 해결책은 HttpRequest를 보내고 반환 데이터를 읽지 않는 것이지만 서버는 처리를 완료하기 전에 스크립트를 삭제할 수 있습니다.

예제 서버 :

<?php
define('FIFO_PATH', '/home/user/input.queue');
define('FORK_COUNT', 10);

if(file_exists(FIFO_PATH)) {
    die(FIFO_PATH . ' exists, please delete it and try again.' . "\n");
}

if(!file_exists(FIFO_PATH) && !posix_mkfifo(FIFO_PATH, 0666)){
    die('Couldn\'t create the listening fifo.' . "\n");
}

$pids = array();
$fp = fopen(FIFO_PATH, 'r+');
for($i = 0; $i < FORK_COUNT; ++$i) {
    $pids[$i] = pcntl_fork();
    if(!$pids[$i]) {
        echo "process(" . posix_getpid() . ", id=$i)\n";
        while(true) {
            $line = chop(fgets($fp));
            if($line == 'quit' || $line === false) break;
            echo "processing (" . posix_getpid() . ", id=$i) :: $line\n";
        //  $data = json_decode($line);
        //  processData($data);
        }
        exit();
    }
}
fclose($fp);
foreach($pids as $pid){
    pcntl_waitpid($pid, $status);
}
unlink(FIFO_PATH);
?>

클라이언트 예 :

<?php
define('FIFO_PATH', '/home/user/input.queue');
if(!file_exists(FIFO_PATH)) {
    die(FIFO_PATH . ' doesn\'t exist, please make sure the fifo server is running.' . "\n");
}

function postToQueue($data) {
    $fp = fopen(FIFO_PATH, 'w+');
    stream_set_blocking($fp, false); //don't block
    $data = json_encode($data) . "\n";
    if(fwrite($fp, $data) != strlen($data)) {
        echo "Couldn't the server might be dead or there's a bug somewhere\n";
    }
    fclose($fp);
}
$i = 1000;
while(--$i) {
    postToQueue(array('xx'=>21, 'yy' => array(1,2,3)));
}
?>

3

백그라운드에서 PHP 스크립트를 실행하는 더 간단한 방법은 다음과 같습니다.

php script.php >/dev/null &

스크립트는 백그라운드에서 실행되며 페이지도 작업 페이지에 더 빠르게 도달합니다.


2

* nix 플랫폼에서 실행 중이라고 가정 cron하고 php실행 파일을 사용 하십시오 .

편집하다:

이미 "cron없이 php 실행"을 요구하는 많은 질문이 있습니다. 다음은 하나입니다.

CRON을 사용하지 않고 스크립트 예약

즉, 위의 exec () 답변은 매우 유망하게 들립니다. :)


감사합니다. 원래 질문을 업데이트했습니다. 이 경우 Cron은 옵션이 아닙니다.
Michael Irigoyen 2011 년

2

Windows를 사용하는 경우 조사 proc_open하거나 popen...

그러나 cpanel을 실행하는 동일한 서버 "Linux"에 있다면 이것이 올바른 접근 방식입니다.

#!/usr/bin/php 
<?php
$pid = shell_exec("nohup nice php -f            
'path/to/your/script.php' /dev/null 2>&1 & echo $!");
While(exec("ps $pid"))
{ //you can also have a streamer here like fprintf,        
 // or fgets
}
?>

사용하지 마십시오 fork()또는 curl당신은 당신이 그들을 처리 할 수 의심 경우, 그냥 서버를 학대처럼

마지막으로 script.php위에서 호출 된 파일 에 다음 과 같이 작성했는지 확인하십시오.

<?php
ignore_user_abort(TRUE);
set_time_limit(0);
ob_start();
// <-- really optional but this is pure php

//Code to be tested on background

ob_flush(); flush(); 
//this two do the output process if you need some.        
//then to make all the logic possible


str_repeat(" ",1500); 
//.for progress bars or loading images

sleep(2); //standard limit

?>

내 휴대 전화를 사용하여 나중에 편집하여 죄송합니다. 실제로 exec () 스크립트를 작성하는 것보다 훨씬 더 어렵습니다. Lol;)
저는 ArbZ입니다.

2

백그라운드 작업자의 경우이 기술을 시도해야한다고 생각합니다. 모든 페이지가 비동기로 각 페이지 응답을 기다리지 않고 한 번에 독립적으로 실행되는 페이지를 호출하는 데 도움이 될 것입니다.

form_action_page.php

     <?php

    post_async("http://localhost/projectname/testpage.php", "Keywordname=testValue");
    //post_async("http://localhost/projectname/testpage.php", "Keywordname=testValue2");
    //post_async("http://localhost/projectname/otherpage.php", "Keywordname=anyValue");
    //call as many as pages you like all pages will run at once //independently without waiting for each page response as asynchronous.

  //your form db insertion or other code goes here do what ever you want //above code will work as background job this line will direct hit before //above lines response     
                ?>
                <?php

                /*
                 * Executes a PHP page asynchronously so the current page does not have to wait for it to     finish running.
                 *  
                 */
                function post_async($url,$params)
                {

                    $post_string = $params;

                    $parts=parse_url($url);

                    $fp = fsockopen($parts['host'],
                        isset($parts['port'])?$parts['port']:80,
                        $errno, $errstr, 30);

                    $out = "GET ".$parts['path']."?$post_string"." HTTP/1.1\r\n";//you can use POST instead of GET if you like
                    $out.= "Host: ".$parts['host']."\r\n";
                    $out.= "Content-Type: application/x-www-form-urlencoded\r\n";
                    $out.= "Content-Length: ".strlen($post_string)."\r\n";
                    $out.= "Connection: Close\r\n\r\n";
                    fwrite($fp, $out);
                    fclose($fp);
                }
                ?>

testpage.php

    <?
    echo $_REQUEST["Keywordname"];//case1 Output > testValue
//here do your background operations it will not halt main page
    ?>

추신 : URL 매개 변수를 루프로 보내려면 다음 답변을 따르십시오 : https://stackoverflow.com/a/41225209/6295712


0

제 경우에는 3 개의 매개 변수가 있는데 그중 하나는 문자열 (mensaje)입니다.

exec("C:\wamp\bin\php\php5.5.12\php.exe C:/test/N/trunk/api/v1/Process.php $idTest2 $idTest3 \"$mensaje\" >> c:/log.log &");

내 Process.php에는 다음 코드가 있습니다.

if (!isset($argv[1]) || !isset($argv[2]) || !isset($argv[3]))
{   
    die("Error.");
} 

$idCurso = $argv[1];
$idDestino = $argv[2];
$mensaje = $argv[3];

0

이것은 나를 위해 작동합니다. tyr이

exec(“php asyn.php”.” > /dev/null 2>/dev/null &“);

게시물 값이 있으면 어떻게합니까? 양식에서 ajax 및 serialize()기능을 통해 보냅니다 . 예를 들어, index.php 양식을 가지고 있고 ajax를 통해 index2.php로 값을 보냅니다. 백그라운드에서 index2.php에서 데이터를 보내고 싶습니다. 감사
칼리드 JA

0

Amphp 를 사용 하여 작업을 병렬 및 비동기식으로 실행 하십시오 .

라이브러리 설치

composer require amphp/parallel-functions

코드 샘플

<?php

require "vendor/autoload.php";

use Amp\Promise;
use Amp\ParallelFunctions;


echo 'started</br>';

$promises[1] = ParallelFunctions\parallel(function (){
    // Send Email
})();

$promises[2] = ParallelFunctions\parallel(function (){
    // Send SMS
})();


Promise\wait(Promise\all($promises));

echo 'finished';

사용 사례에 대해 다음과 같이 할 수 있습니다.

<?php

use function Amp\ParallelFunctions\parallelMap;
use function Amp\Promise\wait;

$responses = wait(parallelMap([
    'a@example.com',
    'b@example.com',
    'c@example.com',
], function ($to) {
    return send_mail($to);
}));
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.