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

    Озадачили меня тут написать демона на 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'ов.

    Скрипт можно еще оптимизировать — доработкой не занимался.
    Кстати, вот от чего я не смог оторваться — браузер все же придется открыть, чтобы изначально запустить скрипт.
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 43

      +1
      А каждую минуту (точнее выставить минимальное время вызова) выполнять скрипт кроном и смотреть прошло нужное время или нет. Т.е. скрипт запускается переодически, но это не значит, что нужное действие будет выполнено.
        0
        Крона нет. Изначально поставлено так, что крон нельзя использовать. Вообще, признаюсь, может сейчас есть другие решения, в частности, в php6 (вообще не знаком с изменениями), т.к. я делал это в декабре 10 года.
          +2
          //зануда_mode=ON: PHP6 не существует и в ближайшее время не будет. Есть PHP 5.3 и разрабатывается PHP 5.4 (уже вроде как в beta)
            0
            Каюсь. Но я уточнил, что про 6 я только слышал, не вникал вообще.
              0
              Кстати, про пхп6 прочитал впервые в книге Мэтта Зандстра «PHP объекты, шаблоны и методики программирования». Не ожидал что там недостоверная информация. Сейчас ради интереса открыл книгу полистал и убедился — в начале книги пишут что пхп6 «находится на пути к нам», а далее в одном из примечаний указано, что в обеих версия пхп5 и пхп6 допустимо импользование var вместо public.
              Видимо поэтому я и думал что пхп6 уже есть.
                0
                Книга какого года?
        +9
        Ни о чем. Из разряда «как я реализовал вполне обычную задачу»

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

        Ну и двойка за исполнение. Что вам не дало просто запустить скрипт, который просто будет выполняться постоянно?

        nohup /path/to/php /path/to/script.php

        > Теперь надо заставить скрипт работать, не обращая внимания на максимальное время выполнения, установленное сервером

        для скриптов, запущенных вручную из консоли этого ограничения нет.

        > Озадачили меня тут написать демона на PHP

        Статья могла бы быть крутой, если бы вы действительно описали то, как писать демона на PHP с использованием php.net/manual/en/book.pcntl.php и php.net/manual/en/book.posix.php (это то, о чем я подумал после прочтения поставновки задачи)
          +1
          Консоли пхп хостинг не давал, о консоли я тогда тоже думал.

          Ссылки открыл, почитаю на досуге, спасибо.
            +1
            Оговорюсь, я понятия не имею какой был хостинг, делал у себя, исходя из того, что говорил заказчик.
              +3
              т.е сейчас даже ssh не дают на хостинге? У вас там какой год?
                +5
                Ну я же дописал специально «оговорюсь....»

                Я делал ни сайт, ни модуль. Просто человек сказал надо сделать вот это, вот так и вот так.
                Я сделал, для меня показалось необычно, я отписал.
                  –6
                  я никогда и никому ssh не даю. ибо нефиг.
                    –1
                    например, agava.ru для шаред хостинга не предоставляет ssh доступ. сам с этим столкнулся. причем это ответ саппорта.
                      0
                      Интересный сарказм. Когда придумали SSH, у нас еще кругом пользовались фидонетом. А понятием хостинга оперировали только избранные. Но, даже тогда уже были rlogin, telnet и rsh.
                      Бугагашенька.
                    +3
                    <?php
                    $demon_script = dirname(__FILE__).'/demon.php';
                    passthru("nohup /usr/bin/php $deamon_script > /dev/null &");
                    ?>

                    В demon.php делаем проверку не запущен ли другой экземляр скрипта.
                  +14
                  image
                    +5
                    После прочтения вступления сложилось впечатление что заказчик нищеброд не может себе позволить хостинг поэтому будет довольствоваться бесплатными без крона. Да и еще программиста напрягает на всю ночную, надеюсь не за 2 копейки?
                      0
                      Думаю, проблема не в том, что не хватает денег на хостинг с кроном, ибо можно воспользоваться каким-нибудь сторонним бесплатным «кроном онлайн», коих развелось множество. Когда-то очень давно сам пользовался ни раз.
                      Кроется здесь что-то более загадочное…
                        0
                        Возможно, это бесплатный хостинг, который предоставляет провайдер, который подключает к интеренту.
                          0
                          Например, что заказчик без понятия, что такое крон или ssh?)
                            0
                            Заказчик, не знающий что это такое, не говорит, что нельзя использовать passthru(), exec(); system(); и не уточняет сразу, что через крон делать нельзя.
                        0
                        > вот от чего я не смог оторваться — браузер все же придется открыть, чтобы изначально запустить скрипт
                        да ладно запустить, а как его остановить предусмотрено?
                          +1
                          В конечном скрипте, отдаваемом заказчику предусмотрено. Здесь не описал, каюсь.
                          +14
                          А мне понравилось. Комментирующие как-то чересчур прагматичны. При этом машина для заваривания чая всем пришлась по душе. :)
                          Автору респект, ему поставили задачу с условиями, и он её выполнил, научившись всему необходимому для этого. В то время, как многие ответили бы заказчику что-нибудь вроде: «купите нормальный хостинг», и даже были бы правы. ))
                            +2
                            Я согласен, — был несколько резок — несколько лет назад я наверное что-нибудь подобное и сделал и от такого счастья, наверно, захотелось бы поделиться тем, что я поборол такую задачу. В общем, автор действительно молодец, что справился с посталенной задачей — на таких задачах скилл как раз и набивается. Думаю, в ответах на этот топик Dep3kuu найдет много информации к размышлению на тему того, как запустить PHP процесс-демон.

                            Вообще, конечно, нужно знать то, где будет запускаться тот софт, который пишешь, — это я бы требовал от заказчика в первую очередь. А как работать без SSH я уже забыл и вспоминать не хочу.
                            0
                            А доступа к system() тоже нет? А то можно было бы тупо один раз запуститься, сделать работу и в конце сделать что-нибудь в духе system("at $run_self_sh_command $some_new_random_time") — запланировать запуск самого себя в какое-то определенное время.

                            Я бы по крайней мере в эту сторону начал думать в первую очередь.
                              0
                              я тоже думал в эту сторону, но это отсеялось. Вернее, я сначала сделал все через passthru, и довольно быстро, но в ответ пришло что нельзя использовать подобное (exec, system в этом контексте считаются подобными).

                              Я не знаю для чего это надо было. Но, судя по оплате результата, на хороший хостинг (я бы сказал даже сервер) деньги были.

                              Да и человек явно или сам знающий, или отдавал код кому-то на проверку.
                              • UFO just landed and posted this here
                                • UFO just landed and posted this here
                                    0
                                    > И не используйте globals, это очень плохо.
                                    Почему?
                                      0
                                      Пояснения есть в книгах и статьх — гораздо более развёрнуто и доступно, чем в формате комментария.

                                      • UFO just landed and posted this here
                                  +3
                                  global $time_sec, $time_to_start; — дальше не читал.
                                    +2
                                    ну остается только возрадоваться, что по ходу был доступ по FTP, да и на интернет или другое средство связи с исполнителем у заказчика были средства.
                                    Ну а так: работает и хорошо, задача выполнена, хотя да, проверок я бы добавил.
                                      0
                                      Вот я понимаю, что в 2011 году это уже звучало как анекдот, но даже сейчас, 5 лет спустя некоторые халявные хостинги не предоставляют клиентам FTP доступ, позволяя работать строго через встроенную систему. Я столкнулся давеча и был невероятно удивлён этим фактом.
                                      0
                                      Автора видимо ни чуть не смущает, что если в одну из итераций запуска «демона» он умрет, то перезапустить его будет некому. Хранение данных в сессии тоже не есть хорошо. Сессии они такие, имеют свойство умирать, особенно когда все находится в одном месте, в /tmp, и админ любит «оптимизировать» нагрузку на фс принудительными зачистками этой директории. И на хостинге где нет даже крона шанс встретить такого «специалиста» очень высок.
                                        +2
                                        Критику я всю выслушал, определенную часть знаний почерпнул, определенная у меня уже была (во-первых, скрипт писался год назад, во вторых, делался он в соответствии с определенными требованиями и ограничениями, ну и основной функционал вообще убран, в том числе, кнопка остановки работы скрипта).

                                        Собственно, в статье целью было не скрипт/программу/продукт/модуль или еще что-то предоставить, а идею. Это не какой-то готовый класс для работы, а пример реализации логики.
                                        Да, задача поставлена необычно, реализовывать пришлось с извращениями + мои ошибки, но может быть кому-то еще пригодится это.

                                        За сим, не понимаю некоторой критики к статье. Однако, учту, ибо статья была первая. Теперь буду знать.
                                          0
                                          Да весело решена зада в своих рамках. Понятно что заковыристо, нестабильно и все такое, но лучше, чем никак.
                                          0
                                          О, госсподи, что ж за кривизна?

                                          Берёте любой html/ajax эмулятор терминала (тот же ajaxterm), запускаете с /bin/bash (/bin/sh и т.д.), подключаетесь, дальше: nohup application &. Если повезло и есть screen, то ещё проще.
                                            0
                                            Спорный вопрос, потому как и аякс и терминал отбросил заказчик.
                                            Однако может аякс он отбросил в том что я хотел сделать, а терминал таким образом можт и можно было запустить, честно, не думал.
                                              0
                                              Кстати… это, возможно, виндовый сервер.: )

                                              Only users with full accounts can post comments. Log in, please.