Фоновое выполнение скрипта на PHP без crontab. Фоновое выполнение скрипта на PHP без crontab Php прервать выполнение скрипта

  • 20.06.2020

Озадачили меня тут написать демона на PHP. Т.е. скрипт, который будет заданное количество раз в заданное количество часов в случайное время (всегда случайное) выполнять определенные действия, и все это без использования cron"a.

До этого никогда не заморачивался, а тут после постановки задачи, начал было думать что так нельзя, что php скрипт надо вызывать браузером…ну задача то поставлена, надо выполнять.

Первая мысль - отключить ограничение времени выполнения скрипта. Запрещено хостером.

Вторая мысль - яваскриптом повторять аякс-запрос периодически (да хоть раз в секунду). - нельзя (требование заказчика).

Выяснилось, собственно, что и браузер открыт не должен быть, и крон нельзя использовать, и работать скрипт должен независимо от пользователя, бесконечно долго. Естественно, он должен минимум грузить систему.

1. Пачка сигарет, ночь, гугл, доки, книги, мануалы….
goto 1…

На выходе получаю:
Задача_1:
Реализовать генератор времен выполнения скрипта, исходя из заданных количества раз и количества часов. Хранить где-то эти времена.

Задача_2:
Работать после закрытия браузера

Задача_3:
Не вылетать после окончания ограничения времени выполнения скрипта

Задача_4:
Выполнять в нужное время какие-то действия.

Итак…
Пишем в конфиге исходные данные:

Session_start(); // Старт сессии $num_starts = 120; // Количество запусков скрипта за промежуток времени $hours = 1; // Количество часов, в течение которых нужно запускать скрипт $num_starts раз. $time_sec = $hours*3600; // Количество секунд в цикле запусков $time_to_start = array(); // Собственно, массив с временами запусков ignore_user_abort(1); // Игнорировать обрыв связи с браузером

Далее пишем функцию, которая поможет нам сгенерировать времена запуска.
В ней мы генерируем случайное число от 0 до количества секунд в исходном интервале.
/****** * @desc Генерируем интервал между запусками. */ function add_time2start() { global $time_sec, $time_to_start; $new_time = time()+rand(0, $time_sec); if (!in_array($new_time, $time_to_start)) { // Если такого времени в массиве нет - добавим $time_to_start = $new_time; } else { add_time2start(); // Если такое время уже есть - генерируем заново. } }

$k = 1; if ($_SESSION["num_st"] == "" || $_SESSION["num_st"][$num_starts-1] < time()) { // проверка, что в сессию не записаны данные и что эти данные не устарели. do { add_time2start($k); $k++; } while ($k < = $num_starts); sort($time_to_start, SORT_NUMERIC); $_SESSION["num_st"] = $time_to_start; }

Теперь надо заставить скрипт работать, не обращая внимания на максимальное время выполнения, установленное сервером.
Принцип таков:
1) Определяем время начала работы скрипта;
2) Определяем установленное ограничение на время выполнения.
3) Запускаем цикл, внутри которого считаем текущее время и вычисляем общее время работы скрипта, сверяем текущее время со значениями в массиве времен запуска, и если совпадение есть, выполняем заданные действия (у меня они в файле exec.php). Для запуска файлов используем сокеты.
4) Повторяем цикл пока время работы скрипта не приблизится к максимально разрешенному. Я поставил - пока до максимального времени не останется 5 секунд.

Итак… считаем начальные данные по времени:

$start_time = microtime(); // Узнаем время запуска скрипта $start_array = explode(" ",$start_time); // Разделяем секунды и миллисекунды $start_time = $start_array; // получаем стартовое время скрипта $max_exec = ini_get("max_execution_time"); //Получаем максимально возможное время работы скрипта
Собственно, цикл. Комментарии в коде.

Do{ $nowtime = time(); // Текущее время //// Если текущее время есть в массиве с временами выполнения скрипта...... if (in_array($nowtime, $_SESSION["num_st"])) { // Сокетом цепляемся к файлу с основным содержанием действий $http = fsockopen("test.ru",80); /// заодно передаем ему данные сессии и время когда он должен сработать fputs($http, "GET http://test.ru/exec.php?".session_name()."=".session_id()."&nowtime=$nowtime HTTP/1.0\r\n"); fputs($http, "Host: test.ru\r\n"); fputs($http, "\r\n"); fclose($http); } //// выполнили заданное действие // Узнаем текущее время чтобы проверить, дальше ли вести цикл или перезапустить $now_time = microtime(); $now_array = explode(" ",$now_time); $now_time = $now_array; // вычитаем из текущего времени начальное начальное $exec_time = $now_time - $start_time+$exec_time; /// тормозимся на секунду usleep(1000000); //Остановка скрипта, работающего в фоновом режиме. Я другого способа не придумал. if (file_exists("stop.txt")) exit; //Проверяем время работы, если до конца работы скрипта //осталось менее 5 секунд, завершаем работу цикла. } while($exec_time < ($max_exec - 5));

Ну и, если разрешенное время подходит к концу, то завершаем цикл и благополучно запускаем этот же скрипт другие процессом (в 5 секунд точно уложимся)

// Запускаем этот же скрипт новым процессом и завершаем работу текущего $http = fsockopen("test.ru",80); fputs($http, "GET http://test.ru/index.php?".session_name()."=".session_id()."&bu=$max_exec HTTP/1.0\r\n"); fputs($http, "Host: test.ru\r\n"); fputs($http, "\r\n"); fclose($http);

Когда дописал все, озадачился полезным применением…Использовать его можно как службу. Он может следить за чем-то в сети и уведомлять Вас, например, по почте. И не надо никаких cron"ов.

Скрипт можно еще оптимизировать - доработкой не занимался.
Кстати, вот от чего я не смог оторваться - браузер все же придется открыть, чтобы изначально запустить скрипт.

8 years ago

If you want to avoid calling exit() in FastCGI as per the comments below, but really, positively want to exit cleanly from nested function call or include, consider doing it the Python way:

define an exception named `SystemExit", throw it instead of calling exit() and catch it in index.php with an empty handler to finish script execution cleanly.

// file: index.php
class SystemExit extends Exception {}
try {
/* code code */
}
catch (SystemExit $e ) { /* do nothing */ }
// end of file: index.php

// some deeply nested function or .php file

If (SOME_EXIT_CONDITION )
throw new SystemExit (); // instead of exit()

?>

10 years ago

Jbezorg at gmail proposed the following:


header ("Location: /" );

?>

After sending the `Location:" header PHP _will_ continue parsing, and all code below the header() call will still be executed. So instead use:

If($_SERVER [ "SCRIPT_FILENAME" ] == __FILE__ )
{
header ("Location: /" );
exit;
}

?>

10 years ago

To rich dot lovely at klikzltd dot co dot uk:

Using a "@" before header() to suppress its error, and relying on the "headers already sent" error seems to me a very bad idea while building any serious website.

This is *not* a clean way to prevent a file from being called directly. At least this is not a secure method, as you rely on the presence of an exception sent by the parser at runtime.

I recommend using a more common way as defining a constant or assigning a variable with any value, and checking for its presence in the included script, like:

in index.php:


in your included file:


BR.

4 years ago

A side-note for the use of exit with finally: if you exit somewhere in a try block, the finally won"t be executed. Could not sound obvious: for instance in Java you never issue an exit, at least a return in your controller; in PHP instead you could find yourself exiting from a controller method (e.g. in case you issue a redirect).

Here follows the POC:



This will print:

testing finally wit exit
In try, exiting

1 year ago

>> Shutdown functions and object destructors will always be executed even if exit is called.

It is false if you call exit into desctructor.

Normal exit:


// Exit into desctructor:

17 years ago

If you are using templates with numerous includes then exit() will end you script and your template will not complete (no , , etc...). Rather than having complex nested conditional logic within your content, just create a "footer.php" file that closes all of your HTML and if you want to exit out of a script just include() the footer before you exit().

include ("header.php");
blah blah blah
if (!$mysql_connect) {
echo "unable to connect";
include ("footer.php");
exit;
}
blah blah blah
include ("footer.php");

16 years ago

Return may be preferable to exit in certain situations, especially when dealing with the PHP binary and the shell.

I have a script which is the recipient of a mail alias, i.e. mail sent to that alias is piped to the script instead of being delivered to a mailbox. Using exit in this script resulted in the sender of the email getting a delivery failure notice. This was not the desired behavior, I wanted to silently discard messages which did not satisfy the script"s requirements.

After several hours of trying to figure out what integer value I should pass to exit() to satisfy sendmail, I tried using return instead of exit. Worked like a charm. Sendmail didn"t like exit but it was perfectly happy with return. So, if you"re running into trouble with exit and other system binaries, try using return instead.

3 years ago

In addition to "void a t informance d o t info", here"s a one-liner that requires no constant:



Placing it at the beginning of a PHP file will prevent direct access to the script.

To redirect to / instead of dying:



Doing the same in a one-liner:



A note to security: Even though $_SERVER["PHP_SELF"] comes from the user, it"s safe to assume its validity, as the "manipulation" takes place _before_ the actual file execution, meaning that the string _must_ have been valid enough to execute the file. Also, basename() is binary safe, so you can safely rely on this function.

7 years ago

When using php-fpm, fastcgi_finish_request() should be used instead of register_shutdown_function() and exit()

For example, under nginx and php-fpm 5.3+, this will make browsers wait 10 seconds to show output: