Как стать автором
Обновить

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

Время на прочтение4 мин
Количество просмотров84K
Озадачили меня тут написать демона на 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[1]; // получаем стартовое время скрипта
$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[1];
        // вычитаем из текущего времени начальное начальное
        $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'ов.

Скрипт можно еще оптимизировать — доработкой не занимался.
Кстати, вот от чего я не смог оторваться — браузер все же придется открыть, чтобы изначально запустить скрипт.
Теги:
Хабы:
Всего голосов 86: ↑39 и ↓47-8
Комментарии43

Публикации

Истории

Ближайшие события

27 августа – 7 октября
Премия digital-кейсов «Проксима»
МоскваОнлайн
11 сентября
Митап по BigData от Честного ЗНАКа
Санкт-ПетербургОнлайн
14 сентября
Конференция Practical ML Conf
МоскваОнлайн
19 сентября
CDI Conf 2024
Москва
20 – 22 сентября
BCI Hack Moscow
Москва
24 сентября
Конференция Fin.Bot 2024
МоскваОнлайн
25 сентября
Конференция Yandex Scale 2024
МоскваОнлайн
28 – 29 сентября
Конференция E-CODE
МоскваОнлайн
28 сентября – 5 октября
О! Хакатон
Онлайн
30 сентября – 1 октября
Конференция фронтенд-разработчиков FrontendConf 2024
МоскваОнлайн