UNIXowy Demon w PHP, cz. 1

Autor: Michał Seth Gołębiowski, dodano: 19-08-2009
Kategoria: Programowanie i tworzenie

W tym artykule będziemy zajmować się demonem UNIXowym, poznając jego podstawowe zadania i zalety oraz dowiemy się jaka jest zasada jego tworzenia w środowisku UNIX. Napiszemy także prosty skrypt, który pozwoli nam w praktyczny i ciekawy sposób wykorzystać potencjał tej funkcjonalności.

Wymagania

Przede wszystkim musze zaznaczyć, że opisywane rozwiązanie będzie działało tylko na systemach UNIXowych (UNIX, Linux, *BSD, MacOS). Mimo, że w systemach z rodziny Windows istnieją podobne funkcje tzw. usługi (services), to ich obsługa znacząco różni się od tych z UNIXów. Dlatego też nie będę poruszał tutaj tej kwestii.

Aby zadziałały przykłady z tego artykułu będziemy potrzebowali PHP w wersji co najmniej 4.3.0 lub wyższej (w tym PHP 5) i co najważniejsze, skompilowane z opcjami --enable-sigchild oraz –enable-pcntl. Opcje te są niezbędne do tego aby nasz skrypt mógł przeistoczyć się w demona – pełnia księżyca czy ofiara złożona z dziewicy w tym wypadku nie wystarczy

Demona będziemy uruchamiać spod konsoli UNIXowej, więc dostęp do niej jest wymagany.

Poza technicznymi aspektami przyda się nam przynajmniej podstawowa znajomość systemów UNIXowych oraz programowania obiektowego (OOP) w PHP.

Czym jest demon UNIXowy i jakie są jego zadania?

Demon UNIXowy to rodzaj programu, który działa nieprzerwanie w tle. Dokładniej jest to aplikacja która tak jak mityczny demon, niezauważalnie dla nas (nie posiada interjfejsu) wykonuje pewne czynności i/lub dostarcza pewnych usług dla systemu operacyjnego i/lub klienta.

Przykładem demona mogą być aplikacje serwerowe takie jak Apache, MySQL czy serwery pocztowe, które to udostępniają nam specyficzne usługi – serwują strony WWW, dają nam dostęp do danych czy naszej poczty elektronicznej.

Reasumując demon to pożyteczny duszek działający w naszym systemie operacyjnym, który jeżeli się z nim dogadamy udostępni nam szereg usług.

Techniczna strona działania demona

Skoro już wiemy czym ogólnie jest demon, przejdźmy do szczegółów w jaki sposób zamienić nasz skrypt na demona.

Jeżeli nie interesuje Cię to jak przebiega cały proces zmiany zwykłego skryptu PHP do postaci demona, możesz spokojnie przejść do następnej części. W przykładach wykorzystamy gotową klasę do obsługi całego procesu demonizacji (trudne słowo), więc szczegóły jej działania nie muszą być Ci znane.

Zasada tworzenia demona UNIXowego wszędzie jest taka sama. Czy to w PHP czy w innych językach dlatego musimy postępować krok po kroku aby proces zakończył się powodzeniem.

Flush

Pierwszym krokiem procesu demonizacji (demonize) jest wyplucie wszystkiego co siedzi w pamięci ekranu. Jest to ważne w przypadku gdy używamy funkcji ob_start() (ob_*), które przechwytują wywołania funkcji echo, print itp. gdyż w późniejszym etapie nie będziemy mogli tego zrobić.

Do tego celu najlepiej użyć funkcji ob_end_flush()

Rozwidlanie

Kolejnym krokiem jest wykonanie rozwidlania (forking). Jak sama nazwa wskazuje będziemy musieli rozwidlić działanie naszego skryptu na dwie części.

Na przykładzie człowieka wyglądało by to jak oddzielenie duszy od ciała. Nasza dusza będzie wtedy duchem (demonem) bez możliwości integracji z fizycznym światem (w naszym przypadku bez możliwości integracji z konsolą systemową) ale nadal będzie egzystować (działać w tle). Natomiast nasze fizyczne ciało umrze (zakończymy działanie skryptu).

Z technicznego punktu widzenia wygląda to tak, że po uruchomieniu komendy fork tworzona jest kopia (klon) procesu i “odczepiana” od konsoli systemowej. Następnie proces rodzica (ten, który wykonał komendę fork) zostaje zamknięty, a proces dziecka nadal jest uruchomiony.

W PHP rozwidlanie wykonujemy za pomocą komendy pcntl_fork(). Zwróci ona nam 3 możliwe wartości.

Pierwsza to -1 oznaczająca błąd. Nasz proces nie rozwidlił się.

Druga to 0 (zero) która oznacza, że utworzono nowy proces, a my jesteśmy rodzicem nowego procesu.

Trzecia to wartość większa od 0 (zero) która oznacza identyfikator procesu dziecka, a jednocześnie mówi nam, że to właśnie my jesteśmy dzieckiem (demonem).

Po pomyślnym zainicjowaniu procesu dziecka (demona), proces rodzica zostaje zakończony (exit()).

Całość kodu PHP potrzebna do wykonania tej operacji podaje poniżej:

$pid = pcntl_fork();

if ($pid == -1) { // Blad fork
    exit('Proces rozwidlania zakonczyl sie niepowodzeniem');
} elseif ($pid) { // Jestesmy rodzicem
    exit();
} else { // Jestesmy dzieckiem (demonem)
    // Tutaj kod naszego demona
}

Nowa tożsamość

W następnym kroku możemy zmienić (chociaż nie musimy), to na jakim koncie systemowym będzie pracował nasz demon.

Domyślnie pracować będzie on na koncie systemowym spod którego został uruchomiony.

Jeżeli nie chcemy aby nasz demon miał uprawnienia do wszystkiego tego do czego my mamy, możemy za pomocą funkcji posix_setgid() oraz posix_setuid() zmienić użytkownika jak i grupę na inną.

Zmiana użytkownika pozwoli nam na ograniczenie praw demonowi co w przypadku np. uruchomienia go z konta roota będzie bardziej niż wskazane.

Pierwsze kroki w dorosłość

Kolejnym ważnym krokiem jest ustawienie naszego procesu jako tzw. “lidera sesji”. Chodzi o to, że nasz rozwidlony skrypt stworzył nowy proces (dziecko), który niejako “zgubił” rodzica. Teraz czas dorosnąć i samemu stać się rodzicem – liderem sesji.

Aby tego dokonać musimy uruchomić funkcję posix_setsid(), która w przypadku powodzenia zwróci nam identyfikator naszego procesu (PID). W przeciwnym wypadku zwróci -1.

Dobre zwyczaje

Przyjęło się, że po stworzeniu demona zapisujemy jego identyfikator procesu (PID) do pliku tak aby później móc sprawdzić czy przypadkiem nie uruchamiamy ponownie tego samego demona, w czasie gdy inny już działa. Jest to także informacja dla nas o tym jaki numer PID przyjął nasz demon – będzie to pomocne chociażby przy wyłączaniu naszego demona.

Aby pobrać numer PID wystarczy uruchomić funkcję posix_getpid(). Plik najlepiej zapisać w katalogu /tmp. Nazwa pliku powinna być taka jak nazwa naszego demona, a jego rozszerzenie to .pid.

Ostatnimi rzeczami które dobrze jest wykonać to ustawienie domyślnej ścieżki naszego demona na / (główna gałąź) oraz domyślnej maski zapisu plików na 0. Robimy to używając odpowiednio komendy chdir() oraz umask().

Haczyk w PHP

Z nie do końca znanych mi przyczyn po powyższych krokach w PHP musimy wykonać jeszcze jedną komendę.

declare(ticks = 1);

Nasz pierwszy demon

Przejdźmy teraz do praktycznego wykorzystania wiedzy, która zdobyliśmy. Napiszemy własnego demona, który będzie pobierał trzy nagłówki z RSSa onet.pl, po czym wyśle nam je SMSem.

Jako, że tylko Plus GSM oferuje prostą metodę na wysłanie SMSa (wysyłamy e-mail na specjalny adres) klienci innych sieci komórkowych będą musieli zadowolić się otrzymaniem samego e-maila z newsami. Mam nadzieje, że ta wiadomość nie zniechęci Was do dalszego czytania gdyż naszym głównym celem jest pokazanie jak można wykorzystać demona, a on Waszej inwencji zależeć będzie co z tego wyjdzie.

Co potrzebujemy?

Jak wcześniej wspomniałem użyjemy gotowej klasy, która całą brudną robotę związaną z inicjacja i obsługą demona wykona za nas.

Do nas będzie należało przygotowanie klasy, której zadaniami będzie pobranie listy nagłówków, wysłanie ich mailem oraz zatrzymanie na odpowiedni czas dalszego wykonywania skryptu.

Klasę do obsługi demona ściągniemy spod adresu http://www.phpclasses.org/browse/package/2197.html

Poza samą klasą (Daemon.class.php) znajduje się tam prosty przykład wykorzystania jej.

Do wysłania maila wykorzystamy gotową bibliotekę o nazwie PHPMailer dostępną pod adresem http://phpmailer.sourceforge.net/

Klasa dla naszego demona

Pierwszą rzeczą jaką musimy zrobić aby zacząć pisać logikę naszego demona to stworzenie klasy, która rozszerzy nam klasę obsługi demona (klasa Daemon). Dodatkowo będziemy musieli nadpisać metodę _doTask(), która wykonywana jest w pętli do momentu zakończenia pracy demona.

To właśnie w tej metodzie będziemy wywoływać metody do pobrania listy newsów oraz wysłania ich mailem.

Nasza klasa powinna więc wyglądać tak:

class OnetNewsDaemon extends Daemon {
    var $rssUrl = 'http://wiadomosci.onet.pl/2,kategoria.rss';
    var $sleepSec = 3600;
    var $newsCount = 3;

    var $smtpHost = '';
    var $smtpUser = '';
    var $smtpPass = '';
    var $senderEmail = '';
    var $senderName = '';
    var $recipientEmail = '';

    var $pidFileLocation = '/tmp/ondaemon.pid';


    function _doTask() {
    }
}

Zmienna $rssUrl to adres RSSa onet.pl, $sleepSec określi nam co jaki czas mamy otrzymywać wiadomość (wartość w sekundach), a $newsCount ile nagłówków mamy pobrać.

Kolejne zmienne określą nam adres naszego serwera pocztowego, z którego będziemy wysyłać wiadomość e-maila. Kolejne to login i hasło naszej skrzynki oraz informacje o nadawcy i odbiorcy.

Jak założyliśmy na wstępie, będziemy wysyłać SMSa na numer komórkowy w sieci Plus GSM.

Plus GSM oferuje nam możliwość wysłania SMSa po przez wysłanie wiadomości na odpowiedni adres e-mail. Ten adres to +48xxxxxxxxx@text.plusgsm.pl gdzie w miejsce X-ów wstawiamy własny numer telefonu. Niestety inni operatorzy mają bardziej skomplikowane sposoby na wysłanie SMSów. Aby więc nie komplikować przykładu skupimy się tylko na tej sieci, a dla pozostałych zostaje podanie prywatnego adresu e-mail na który zostanie wysłana wiadomość.

Zmienna $pidFileLocation nadpisuje nam domyślną wartość klasy Daemon, podając ścieżkę do pliku z numerem procesu demona (PID).

Pobieranie nagłówków RSS

Czas zabrać się za metodę pobierającą listę nagłówków z kanału RSS portalu onet.pl.

Do tego celu użyjemy prostego wyrażenia regularnego, które zwróci nam listę nagłówków.

Następnie zamienimy polskie znaki na ich “bezogonkowe” odpowiedniki. Robi tak aby nasza wiadomość z powodu polskich znaków nie zajmował więcej niż jeden SMS.

Metoda do pobierania nagłówków oraz zamiany polskich znaków na ich odpowiedniki, wygląda następująco:

function _getNewsList($limit = 3) {
    $newsList = array();
    $result = preg_match_all('/<title>(.*)</title>/Umi', @file_get_contents($this->rssUrl), $match);

    if ($result) {
        for ($i = 1; $i < $limit; $i++) {
            $newsList[] = $this->_clearPolishChars($match[1][$i]);
        }

        return $newsList;
    } else {
        return false;
    }
}

function _clearPolishChars($text) {
    $pl = array (
        '&#260;' => 'A',
        '&#261;' => 'a',
        '&#262;' => 'C',
        '&#263;' => 'c',
        '&#280;' => 'E',
        '&#281;' => 'e',
        '&#321;' => 'L',
        '&#322;' => 'l',
        '&#323;' => 'N',
        '&#324;' => 'n',
        '&#211;' => 'O',
        '&#243;' => 'o',
        '&#346;' => 'S',
        '&#347;' => 's',
        '&#377;' => 'Z',
        '&#378;' => 'z',
        '&#379;' => 'Z',
        '&#380;' => 'z'
    );
    return strtr($text, $pl);
}


Metoda _getNewsList() w przypadku powodzenia zwraca nam tablice z listą nagłówków, a w przypadku niepowodzenia wartość FALSE.

Z kolei metoda _clearPolishChars() zamienia nam polskie ogonki (w standardzie UTF-8) na ich “bezogonkowe” odpowiedniki.

Wysyłanie wiadomości

Metoda odpowiedzialna za wysłanie e-maila na podany przez nas adres jest jeszcze prostsza niż poprzednia:

function _sendMail($text) {
    static $mail = null;

    if (!$mail) {
        $mail = new PHPMailer();

        $mail->Host     = $this->smtpHost;
        $mail->Mailer   = 'smtp';

        $mail->From     = $this->senderEmail;
        $mail->FromName = $this->senderName;
        $mail->AddAddress($this->recipientEmail, $this->recipientEmail);

        $mail->Subject  = 'OnetNewsDaemon';
    }

    $mail->Body = $text;

    return $mail->Send();
}

Sądzę, że jedyną rzeczą którą warto wyjaśnić jest zadeklarowanie zmiennej $mail jako zmienną statyczną. Jako, że demon wykonuje się bez przerwy, możemy dla przyspieszenia wykonania skryptu zachować pewne zmienne w pamięci komputera, bez potrzeby inicjowania ich za każdym wejściem do funkcji.

Obiekt klasy PHPMailer będziemy wykorzystywać zawsze podczas wysyłania e-maila, tak więc zainicjujemy go tylko raz.

I tutaj jedna ważna uwaga. Jako, że demon działa cały czas musimy pamiętać o tym, że niektóre zmienne czy też wskaźniki np. do pliku nie zostaną od razu zamknięte. Może to wywoływać konflikty dostępu do danych bądź sprawić, że demon będzie zabierał coraz więcej pamięci komputera. Projektując więc nasz skrypt zwróćmy na to uwagę i stosujmy tam gdzie jest to potrzebne funkcje unset(), fclose() itp.

Zapraszam do części drugiej artykułu.

Ocena 2.66/5 (53.18%) (481 głosów)

Komentarze:


  • Dodał: spin data: 2010-02-01
    extra art :)

  • Dodał: Gość data: 2012-07-01
    Za taki kod, powinni karać... dramat :/


Dodaj komentarz:


Temat:
Twój nick:
Komentarz:
 

Prosimy o kulturę wypowiedzi. Komentarze zawierające niecenzuralne zwroty, bądź obrażające inne osoby będą usuwane. Kod HTML w wypowiedziach jest niedozwolony. Wydawca nie odpowiada za treść komentarzy.