Làm cách nào tôi có thể chạy tập lệnh PHP trong nền sau khi gửi biểu mẫu?


104

Vấn đề
Tôi có một biểu mẫu, khi được gửi, sẽ chạy mã cơ bản để xử lý thông tin được gửi và chèn nó vào cơ sở dữ liệu để hiển thị trên trang web thông báo. Ngoài ra, tôi có danh sách những người đã đăng ký nhận những thông báo này qua email và tin nhắn SMS. Danh sách này hiện tại là không đáng kể (chỉ khoảng 150), tuy nhiên, nó đủ để khiến bạn phải mất hơn một phút để lướt qua toàn bộ bảng người đăng ký và gửi hơn 150 email. (Các email đang được gửi riêng lẻ theo yêu cầu của quản trị viên hệ thống của máy chủ email của chúng tôi do các chính sách email hàng loạt.)

Trong thời gian này, cá nhân đã đăng cảnh báo sẽ ngồi ở trang cuối cùng của biểu mẫu trong gần một phút mà không có bất kỳ sự củng cố tích cực nào rằng thông báo của họ đang được đăng. Điều này dẫn đến các vấn đề tiềm ẩn khác, tất cả những gì có giải pháp khả thi mà tôi cảm thấy đều kém lý tưởng.

  1. Đầu tiên, người đăng có thể nghĩ rằng máy chủ đang bị chậm và nhấp lại vào nút 'Gửi', khiến tập lệnh bắt đầu lại hoặc chạy hai lần. Tôi có thể giải quyết điều này bằng cách sử dụng JavaScript để tắt nút và thay thế văn bản để nói điều gì đó như 'Đang xử lý ...', tuy nhiên điều này ít lý tưởng hơn vì người dùng sẽ vẫn bị mắc kẹt trên trang trong suốt thời gian thực thi tập lệnh. (Ngoài ra, nếu JavaScript bị tắt, sự cố này vẫn tồn tại.)

  2. Thứ hai, người đăng có thể đóng tab hoặc trình duyệt sớm sau khi gửi biểu mẫu. Tập lệnh sẽ tiếp tục chạy trên máy chủ cho đến khi nó cố gắng ghi lại vào trình duyệt, tuy nhiên nếu sau đó người dùng duyệt đến bất kỳ trang nào trong miền của chúng tôi (trong khi tập lệnh vẫn đang chạy), trình duyệt sẽ treo trang tải cho đến khi tập lệnh kết thúc . (Điều này chỉ xảy ra khi một tab hoặc cửa sổ của trình duyệt bị đóng chứ không phải toàn bộ ứng dụng trình duyệt.) Tuy nhiên, điều này không lý tưởng hơn.

(Có thể) Giải pháp
Tôi đã quyết định chia nhỏ phần "email" của script thành một tệp riêng biệt mà tôi có thể gọi sau khi thông báo đã được đăng. Ban đầu tôi nghĩ đến việc đưa thông tin này lên trang xác nhận sau khi thông báo đã được đăng thành công. Tuy nhiên, người dùng sẽ không biết tập lệnh này đang chạy và bất kỳ sự bất thường nào sẽ không rõ ràng đối với họ; Tập lệnh này không thể bị lỗi.

Nhưng, điều gì sẽ xảy ra nếu tôi có thể chạy tập lệnh này như một quy trình nền? Vì vậy, câu hỏi của tôi là: Làm thế nào tôi có thể thực thi một tập lệnh PHP để kích hoạt như một dịch vụ nền và chạy hoàn toàn độc lập với những gì người dùng đã thực hiện ở cấp biểu mẫu?

CHỈNH SỬA: Điều này không thể được cron'ed. Nó phải chạy ngay khi biểu mẫu được gửi. Đây là những thông báo có mức độ ưu tiên cao. Ngoài ra, quản trị viên hệ thống đang chạy máy chủ của chúng tôi không cho phép kẻ gian chạy thường xuyên hơn 5 phút.


Bắt đầu một công việc cron và thông báo cho người dùng trên trang khác rằng yêu cầu của họ đang được xử lý hoặc tương tự như vậy.
Evan Mulawski

1
Bạn có muốn chạy nó thường xuyên (hàng ngày hoặc hàng giờ) hay đang gửi không? Tôi đoán bạn có thể sử dụng php exec()nhưng tôi chưa bao giờ thử điều này.
Simon

2
Tôi chỉ đơn giản sử dụng ajaxvà chèn sleep()khoảng thời gian bên trong vòng lặp (tôi sử dụng foreach trong trường hợp của mình) để chạy quy trình nền. Nó hoạt động hoàn hảo trong máy chủ của tôi. Không chắc chắn về những người khác. Tôi cũng đang thêm ignore_user_abort()set_time_limit()(với thời gian kết thúc được tính toán) trước vòng lặp để đảm bảo rằng tập lệnh sẽ không bị dừng bởi máy chủ mà tôi sử dụng, ngay cả theo thử nghiệm của tôi, tập lệnh hoàn thành tác vụ mà không có ignore_user_abort()set_time_limit().
Ari

Tôi thấy bạn đã đưa ra một giải pháp. Tôi sử dụng gearmanvới php để giải quyết các tác vụ nền. Nó hoàn hảo để mở rộng quy mô nếu bạn gặp phải quá trình xử lý thời gian lớn và tải nặng. Bạn nên xem xét nó.
Andre Garcia

Thực hiện theo câu trả lời này trên [stackoverflow] ( stackoverflow.com/a/46617107/6417678 )
Joshy Francis

Câu trả lời:


89

Thực hiện một số thử nghiệm execshell_exectôi đã phát hiện ra một giải pháp hoạt động hoàn hảo! Tôi chọn sử dụng shell_execđể tôi có thể ghi lại mọi quá trình thông báo xảy ra (hoặc không). ( shell_exectrả về dưới dạng một chuỗi và điều này dễ dàng hơn so với việc sử dụng exec, gán đầu ra cho một biến và sau đó mở tệp để ghi vào.)

Tôi đang sử dụng dòng sau để gọi tập lệnh email:

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

Điều quan trọng là phải lưu ý &ở cuối lệnh (như được chỉ ra bởi @netcoder). Lệnh UNIX này chạy một tiến trình trong nền.

Các biến phụ được đặt trong dấu ngoặc kép sau đường dẫn đến tập lệnh được đặt làm $_SERVER['argv']biến mà tôi có thể gọi trong tập lệnh của mình.

Sau đó, tập lệnh email sẽ xuất ra tệp nhật ký của tôi bằng cách sử dụng >>và sẽ xuất ra một cái gì đó như sau:

[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)

Cảm ơn. Điều này đã cứu mạng tôi.
Liam Bailey

Đó là gì $post_idsau đường dẫn của tập lệnh php?
Perocat

@Perocat nó là một biến mà bạn đang gửi tới tập lệnh. Trong trường hợp này, nó đang gửi một ID sẽ là một tham số cho tập lệnh được gọi.
Chique

Tôi nghi ngờ điều này không hoạt động hoàn hảo, hãy xem stackoverflow.com/questions/2212635/…
symcbean

1
Có một đề xuất chỉnh sửa đang chờ xử lý để thoát $post_id; Tôi thà cast nó trực tiếp đến một số: (int) $post_id. (Kêu gọi sự chú ý của bạn để quyết định cái nào tốt hơn.)
Al.G.

19

Trên máy chủ Linux / Unix, bạn có thể thực thi công việc ở chế độ nền bằng cách sử dụng 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);

Điều &quan trọng là ở đây. Kịch bản sẽ tiếp tục ngay cả khi kịch bản gốc đã kết thúc.


Điều này hoạt động khi tôi không gọi proc_closekhi nhiệm vụ đã được hoàn thành. proc_closelàm cho tập lệnh bị treo khoảng 15 đến 25 giây. Tôi cần giữ nhật ký bằng cách sử dụng thông tin ống dẫn, vì vậy tôi phải mở một tệp để ghi vào, đóng nó, rồi đóng kết nối proc. Vì vậy, rất tiếc, giải pháp này không làm việc cho tôi.
Michael Irigoyen

3
Tất nhiên bạn không gọi proc_close, đó là vấn đề! Và có, bạn có thể chuyển đến một tệp với proc_open. Xem câu trả lời cập nhật.
netcoder

@netcoder: điều gì sẽ xảy ra nếu tôi không muốn lưu trữ kết quả trong bất kỳ tệp nào. những thay đổi nào được yêu cầu trong stdout?
naggarwal

9

Trong số tất cả các câu trả lời, không có câu trả lời nào được coi là hàm fastcgi_finish_request dễ dàng một cách kỳ lạ , khi được gọi, nó sẽ chuyển tất cả đầu ra còn lại vào trình duyệt và đóng phiên Fastcgi và kết nối HTTP, đồng thời cho phép tập lệnh chạy ở chế độ nền.

Một ví dụ:

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

đây chính xác là những gì tôi đang tìm kiếm, trong shell_exec, bằng cách nào đó tôi phải chuyển dữ liệu đã đăng sang tập lệnh được thực thi, bản thân nó là một nhiệm vụ tốn thời gian trong trường hợp của tôi và cũng khiến mọi thứ trở nên rất phức tạp.
Aurangzeb

7

PHP exec("php script.php")có thể làm điều đó.

Từ Sách hướng dẫn :

Nếu một chương trình được khởi động với chức năng này, để nó tiếp tục chạy ở chế độ nền, đầu ra của chương trình phải được chuyển hướng đến một tệp hoặc một luồng đầu ra khác. Không làm như vậy sẽ khiến PHP bị treo cho đến khi quá trình thực thi chương trình kết thúc.

Vì vậy, nếu bạn chuyển hướng đầu ra đến tệp nhật ký (dù sao cũng là một ý kiến ​​hay), tập lệnh gọi của bạn sẽ không bị treo và tập lệnh email của bạn sẽ chạy trong bg.


1
Cảm ơn, nhưng điều này không hoạt động. Tập lệnh vẫn "treo" trong khi nó xử lý tệp thứ hai.
Michael Irigoyen

Nhìn vào khen php.net/manual/en/function.exec.php#80582 Có vẻ như điều này là do safe_mode được kích hoạt. Có một cách giải quyết trong unix.
Simon

Rất tiếc, chế độ an toàn không được bật trong cài đặt của chúng tôi. Thật kỳ lạ là nó vẫn đang treo.
Michael Irigoyen

6

Và tại sao không thực hiện Yêu cầu HTTP trên tập lệnh và bỏ qua phản hồi?

http://php.net/manual/en/ Chức năng.httprequest-send.php

Nếu bạn thực hiện yêu cầu của mình trên tập lệnh, bạn cần gọi máy chủ web của mình sẽ chạy nó trong nền và bạn có thể (trong tập lệnh chính của mình) hiển thị thông báo cho người dùng biết rằng tập lệnh đang chạy.


4

Còn cái này thì sao?

  1. Tập lệnh PHP của bạn giữ biểu mẫu lưu một cờ hoặc một số giá trị vào cơ sở dữ liệu hoặc tệp.
  2. Tập lệnh PHP thứ hai sẽ thăm dò định kỳ giá trị này và nếu nó được đặt, nó sẽ kích hoạt tập lệnh Email một cách đồng bộ.

Tập lệnh PHP thứ hai này nên được đặt để chạy dưới dạng cron.


Cảm ơn bạn, nhưng tôi đã cập nhật câu hỏi ban đầu. Cron's không phải là một lựa chọn trong trường hợp này.
Michael Irigoyen

4

Như tôi biết bạn không thể làm điều này một cách dễ dàng (xem fork Ex, v.v. (không hoạt động trong cửa sổ)), có thể bạn có thể đảo ngược cách tiếp cận, sử dụng nền của trình duyệt đăng biểu mẫu trong ajax, vì vậy nếu bài đăng vẫn làm việc mà bạn không có thời gian chờ đợi.
Điều này có thể hữu ích ngay cả khi bạn phải thực hiện một số công đoạn dài.

Về việc gửi thư, bạn luôn nên sử dụng một bộ đệm, có thể là một máy chủ smtp cục bộ và nhanh chóng chấp nhận các yêu cầu của bạn và chuyển chúng đến MTA thực hoặc đặt tất cả vào một DB, thay vì sử dụng một cron để cuộn hàng đợi.
Cron có thể nằm trên một máy khác gọi bộ đệm là url bên ngoài:

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

3

Công việc cron nền nghe có vẻ là một ý tưởng hay cho việc này.

Bạn sẽ cần quyền truy cập ssh vào máy để chạy tập lệnh dưới dạng cron.

$ php scriptname.php để chạy nó.


1
không cần ssh. Nhiều máy chủ không cho phép ssh, nhưng có hỗ trợ cron.
Teson

Cảm ơn bạn, nhưng tôi đã cập nhật câu hỏi ban đầu. Cron's không phải là một lựa chọn trong trường hợp này.
Michael Irigoyen

3

Nếu bạn có thể truy cập máy chủ qua ssh và có thể chạy các tập lệnh của riêng mình, bạn có thể tạo một máy chủ năm mươi đơn giản bằng php (mặc dù bạn sẽ phải biên dịch lại php với posixsự hỗ trợfork ).

Máy chủ có thể được viết bằng bất cứ thứ gì thực sự, bạn có thể dễ dàng làm điều đó bằng python.

Hoặc giải pháp đơn giản nhất sẽ là gửi một HttpRequest và không đọc dữ liệu trả về nhưng máy chủ có thể hủy tập lệnh trước khi nó xử lý xong.

Máy chủ mẫu:

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

Khách hàng mẫu:

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

Cách đơn giản hơn để chạy một tập lệnh PHP trong nền là

php script.php >/dev/null &

Tập lệnh sẽ chạy trong nền và trang cũng sẽ đến trang hành động nhanh hơn.


2

Giả sử bạn đang chạy trên nền tảng * nix, hãy sử dụng cronphp tệp thực thi.

BIÊN TẬP:

Có khá nhiều câu hỏi "chạy php mà không cần cron" trên SO rồi. Đây là một:

Lên lịch tập lệnh mà không cần sử dụng CRON

Điều đó nói rằng, câu trả lời execute () ở trên nghe có vẻ rất hứa hẹn :)


Cảm ơn bạn, nhưng tôi đã cập nhật câu hỏi ban đầu. Cron's không phải là một lựa chọn trong trường hợp này.
Michael Irigoyen

2

Nếu bạn đang sử dụng Windows, hãy nghiên cứu proc_openhoặc popen...

Nhưng nếu chúng ta đang ở trên cùng một máy chủ "Linux" đang chạy cpanel thì đây là cách tiếp cận phù hợp:

#!/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
}
?>

Không sử dụng fork()hoặc curlnếu bạn nghi ngờ mình có thể xử lý chúng, nó giống như lạm dụng máy chủ của bạn

Cuối cùng, trên script.phptệp được gọi ở trên, hãy lưu ý điều này, đảm bảo rằng bạn đã viết:

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

?>

Xin lỗi vì đã chỉnh sửa nó sau đó vì tôi đang sử dụng điện thoại di động của tôi và thực sự thì nó khó hơn viết một đoạn script thực thi (). Lol;)
tôi là ArbZ

2

đối với background worker, tôi nghĩ bạn nên thử kỹ thuật này, nó sẽ giúp gọi bao nhiêu trang bạn thích, tất cả các trang sẽ chạy cùng một lúc một cách độc lập mà không cần đợi phản hồi của mỗi trang là không đồng bộ.

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

Tái bút: nếu bạn muốn gửi tham số url dưới dạng vòng lặp thì hãy làm theo câu trả lời sau: https://stackoverflow.com/a/41225209/6295712


0

Trong trường hợp của tôi, tôi có 3 tham số, một trong số chúng là chuỗi (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 &");

Trong Process.php của tôi, tôi có mã này:

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

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

0

Đây là công việc cho tôi. đánh máy cái này

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

nếu tôi có giá trị bài viết thì sao? từ một biểu mẫu và tôi gửi nó qua ajax và serialize()function. ví dụ: tôi có index.php với biểu mẫu và tôi gửi giá trị qua ajax tới index2.php Tôi muốn thực hiện gửi dữ liệu trong index2.php ở chế độ nền, bạn đề xuất gì? cảm ơn
khalid JA

0

Sử dụng Amphp để thực hiện các công việc song song & không đồng bộ.

Cài đặt thư viện

composer require amphp/parallel-functions

Mẫu mã

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

Đối với trường hợp sử dụng của bạn, Bạn có thể làm một số việc như dưới đây

<?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);
}));
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.