Alessio Treglia

everybody lies
  • Quality Assurance
  • EN blog
  • Inizio
  • Chi sono
  • Roma3 WiFi Authenticator
  • Installation Report Generator
  • Preventivi e consulenze

Un Python demoniaco alla fermata del bus!

quadrispro | 12 febbraio 2008

I miei studi sul Python proseguono a gonfie vele e oggi vi presento un piccolo HOWTO su come scrivere un demone nel linguaggio di programmazione più portabile del mondo (e a chi, in questo momento, viene in mente solo la parola Java dico: “Seee! Te piacerebbe!“).

Andiamo per gradi, iniziamo a rispondere alla prima domanda: cos’è un demone? Cito Wikipedia:

Nei sistemi Unix, e più in generale nei sistemi operativi multitasking, un demone (daemon in inglese) è un programma eseguito in background, senza che sia sotto il controllo diretto dell’utente. Di solito i demoni hanno nomi che finiscono per “d”: per esempio, syslogd è il demone che gestisce i log di sistema.

In ambiente Windows un tale programma è noto con il nome di Servizio.

Angeli e diavoli

Insomma, un demone è un applicazione residente in memoria che può avere, come compiti principali, quello di rispondere a delle richieste da parte di altri programmi e/o eseguire periodicamente delle procedure; questo tipo di soluzione è molto utile nel caso si intenda sviluppare un’applicazione distribuita secondo un modello client/server e nel seguente esempio verrà implementato un piccolo demone, il quale avrà come unico scopo quello di rispondere alle richieste ricevute tramite DBus, il bus di sistema di GNOME. Se pensate che possa essere un compito difficile, ve ne farò ricredere!

Ok, siamo pronti, cominciamo: innanzitutto entriamo nella nostra Home e creiamoci una cartella dove andremo a posizionare i sorgenti:

cd ~
mkdir test

Dunque creiamo il file che conterrà il codice del nostro infernale componente software:

gedit ~/test/daemon.py

Ecco fatto! Ora non resta che copiare e incollare all’interno dell’editor il seguente codice, al cui interno ho già inserito numerosi commenti, i quali, grazie anche all’estrema semplicità del linguaggio, saranno sicuramente più che sufficienti per comprendere il funzionamento del demone:

#!/usr/bin/env python

# File: daemon.py

# Spostiamoci nella directory corrente
import os,sys
if __name__ == '__main__':
        os.chdir(os.path.dirname(os.path.normpath(os.path.join(os.getcwd(),sys.argv[0]))))
# Importiamo i moduli necessari
import gobject
import dbus
import dbus.service
if getattr(dbus, 'version', (0,0,0)) >= (0,41,0):
        import dbus.glib

LOG_DIR = '/tmp'    # Questa ci serve per memorizzare il file .pid

# Il nostro oggetto condiviso
class Demone(dbus.service.Object):
    def __init__(self, bus_name, object_path='/org/test/daemon'):
    # è necessario richiamare il costruttore della classe base
        dbus.service.Object.__init__(self, bus_name, object_path)
    ## Eccoci ad un punto saliente finalmente!
    ## Questo "stupido" metodo, che verrà richiama dalla nostra
    ## applicazione, fa solo una cosa: stampa a schermo un
    ## simpatico saluto (strano per un *demone*, vi pare?).
    @dbus.service.method('org.test.daemon.prova')
    def Hello(self):
        sys.stdout.write('Hello world from a dbus daemon!\n')
        sys.stdout.flush()
        return 0
    #end function Hello

############## SIGNAL HANDLERS #################
## Qui raccogliamo tutti i metodi che gestiranno i segnali ricevuti.
## In questo caso ci interessa gestire solo SIGTERM.
def terminate(signal, param):
	try:
		# Esegue tutte le operazioni necessarie alla corretta
                # chiusura dell'applicazione

		# Cancella il file .pid
		os.remove(os.path.join(LOG_DIR, 'run.pid'))
	except:
		pass                            # Ignora tutti gli errori...
        ## ...e stampa un messaggio di addio!
	sys.stdout.write("........terminating\n")
	sys.exit(0)
############## END SIGNAL HANDLERS ##############

## Analizza e restituisce lo stato attuale del demone (se è attivo
## o meno). Restituisce il process id del demone se è attualmente
## presente in memoria, -1 in caso contrario.
def status():
    if os.path.isfile(os.path.join(LOG_DIR, 'run.pid')):
        f = open(os.path.join(LOG_DIR, 'run.pid'), 'r')
        pid = string.atoi(string.strip(f.readline()))
        f.close()
        try:
            os.kill(pid, 0)
        except os.error, args:
            if args[0] != errno.ESRCH: # Nessun processo
                raise os.error, args
        else:
            # il demone è avviato
            return pid
    # il demone non è presente in memoria
    return -1

## Quelle che seguono sono le istruzioni relative alla gestione
## dei processi del demone: verranno creati due fork, in questo modo
## sarà possibile "staccare" il programma dal terminale e farne
## continuare l'esecuzione in background.
def start():
    try:
        # procediamo con il primo fork...
        pid = os.fork()
        if pid > 0:
            # chiudiamo il processo padre...
            sys.exit(0)
    except OSError, e:
        print >>sys.stderr, "fork #1 failed: %d (%s)" % (e.errno, e.strerror)
        sys.exit(1)

	# "stacchiamolo" dal processo principale...
    os.setsid()
    os.umask(0) # ...e diamogli i giusti permessi!

    # Ora procediamo con il secondo fork...
    try:
        pid = os.fork()
        if pid > 0:
            print "test daemon: pid " + str(pid)
            sys.exit(0)
    except OSError, e:
        print >>sys.stderr, "fork #2 failed: %d (%s)" % (e.errno, e.strerror)
        sys.exit(1)

    # Controlla l'esistenza della directory del file di log. Se non esiste, la crea.
    if not os.path.isdir(LOG_DIR):
        os.mkdir(LOG_DIR, 0755)

    # Infine scrive l'id del processo nel file .pid.
    f = open(os.path.join(LOG_DIR, 'run.pid'), 'w')
    f.write("%d" % os.getpid())
    f.close()

    return 0

## Se start() serve per avviarlo, immaginate un po' a che serve
## questo... Se il demone è attivo lo termina e restituisce 0, altrimenti
## stampa un avviso e restituisce -1.
def stop():
    ## Controlliamo la presenza del file .pid...
    if os.path.isfile(os.path.join(LOG_DIR, 'run.pid')):
        ## apre il file e legge l'id del processo...
        f = open(os.path.join(LOG_DIR, 'run.pid'), 'r')
        pid = string.atoi(string.strip(f.readline()))
        f.close()
        try:
            os.kill(pid, 0)  ## ...dunque prova a killarlo...
        except os.error, args:
            if args[0] != errno.ESRCH: ## ...se non trova il processo
                raise os.error, args ## lancia un'eccezione...
        else:
            os.kill(pid, signal.SIGTERM) ##....altrimenti invia un SIGTERM
            return 0                          ## al demone
    return -1

# Memorizza il nome del programma.
call_name = os.path.split(sys.argv[0])[1]

## Riceve gli argomenti passati sulla riga di comando. Stabiliamo di
## processarne 3, "start", "status" e "stop", uno per ogni metodo di
## quelli definiti in precedenza.
if len(sys.argv) != 2:
    sys.stderr.write("Usage: %s [start|stop|status]\n" % call_name)
    sys.exit(1)
if len(sys.argv) == 2:
    if sys.argv[1] == 'stop':
        # "Stoppa" il demone
        s = stop()
        if s == 1:     ## se il demone non è attivo stampa un avviso...
            sys.stdout.write("%s: daemon is not running\n" % call_name)
            sys.exit(1) ## ...e esci.
        sys.stdout.write("%s: stopped.\n" % call_name)
        sys.exit(0)
    elif sys.argv[1] == 'status':
        # Stampa lo stato del demone.... beh, questa è facile, no?
        s = status()
        if s == -1:
            sys.stdout.write("%s: daemon is not running\n" % call_name)
        else:
            sys.stdout.write("%s: daemon [%d] is running\n" % (call_name, s))
            sys.exit(0)
    elif sys.argv[1] == 'start':
        # Oh ecco, qui avviamo il demone, verificando prima che non sia già
        # già stato caricato in memoria.
        s = status()
        if s != -1:
            sys.stdout.write("%s: daemon [%d] is running\n" % (call_name, s))
            sys.exit(1)
        start()
else:
    ## e qui ricordiamo all'utente come funziona il nostro demone....
    sys.stderr.write("Usage: %s [stop|status]\n" % call_name)
    sys.exit(1)

## Terminata la parte "infernale", ovvero quella relativa al demone,
## passiamo ad occuparci del lato server.
# Apriamo la connessione con DBus e "attacchiamoci" al bus della sessione.
session_bus = dbus.SessionBus() # Per un demone di sistema va sostituito con dbus.SystemBus()
try:
# Creiamo, inizializziamo e "attacchiamo" l'oggetto a DBus
    bus_name = dbus.service.BusName('org.test.daemon', bus=session_bus)
    object = Demone(bus_name)
except Exception, e:
    print "Errore ", e
# Ora non resta che avviare il loop principale
mainloop = gobject.MainLoop()
mainloop.run()
# Il demone è pronto e già freme nell'attesa di
# rispondere alle nostre richieste!

Ok, salvato il file apriamo un terminale e diamogli i giusti permessi con

chmod +x ~/test/daemon.py

Finito! Per avviarlo basta digitare il nome del comando seguito dall’opzione start:

~/test/daemon.py start

Successivamente, per stopparlo, basterà sostituire start con stop.

Se si vuole essere sicuri che tutto sia andato a buon fine, su Ubuntu è possibile andare su Sistema -> Amministrazione -> Monitor di sistema e verificare con i propri occhi la presenza del demone fra i processi residenti in memoria:

Monitor di sistema

Soddisfatti? Io molto e sì, sono uno che si diverte con davvero poco! Ma soprattutto mi diverte farmi stupire da Python, un linguaggio davvero sbalorditivo per come riesce a unire potenza, semplicità e grandi performance (e qui mi viene da pensare a Java: “oh! come rido!” :D )

Alla prossima puntata, dove scriveremo una piccola applicazione capace di interagire con il nostro piccolo diavolo!

Categorie
GNOME, Guide e howto, Java, Open source & free software, Python, Ubuntu
Commenti RSS
Commenti RSS
Trackback
Trackback

« Pep-pepepeeee! Pep-pepepeeee! Pep-pepepee-pepeeee!! La guida per il mio portatile sul wiki di Ubuntu-it »

12 Responses to “Un Python demoniaco alla fermata del bus!”

  1. devil scrive:
    30 maggio 2008 alle 14:20

    hai proprio ragione, sono agli inizi, ma python è un linguaggio con delle incredibili potenzialità, e si può unire anche ad altri linguaggi

    Replica
  2. Michele scrive:
    25 luglio 2008 alle 00:58

    ma la libreria gobject dove la prendi? =)
    Anche io vorrei fare una specie di demone, o servizio in windows, un programma che resti in esecuzione e svolga il suo compito in background ogni 15 minuti.
    mi saresti una mano?

    Saluti

    Replica
  3. Paride scrive:
    10 febbraio 2009 alle 10:28

    Cioa,
    ho provato a fare il copia ed incolla del tuo codice, ma mi esce sempre tutto sfasato e quando cerco di eseguirlo ho errori vari sull’indentazione.
    Ho provato anche a copiarlo riga per riga, ma nulla.
    Ho anche degli errori sulle vocali accentate nei commenti.
    potresto mettere un link ad un file da scaricare dirtettamente con il sorgente, o in alternativa, visto che hai la mia mail, spedirmelo?
    Sto iniziando anche io a programmare in python, e so che questo non e’ il modo migliore, iniziare direttamente dai demoni, ma mi servirebbe capire come funziona (sperimentarlo) per un progettino che vorrei realizzare.
    In COBOL (linguaggio che conosco) non si puo’ demonizzare nulla.

    Replica
  4. Paride scrive:
    10 febbraio 2009 alle 10:37

    Aggiornamento: sono riuscito a farglielo eseguire ma avviandolo con start ricevo solo:

    test daemon: pid 3549

    Se invece gli do status, ricevo:

    paride@imladris:~/work$ ./daemon.py status
    Traceback (most recent call last):
    File “./daemon.py”, line 150, in
    s = status()
    File “./daemon.py”, line 57, in status
    pid = string.atoi(string.strip(f.readline()))
    NameError: global name ’string’ is not defined

    Cosa sto sbagliando?
    La versione di python che uso e’ la 2.5.2 di Debian Lenny

    Replica
    • quadrispro scrive:
      10 febbraio 2009 alle 13:30

      prova con

      import string

      Replica
    • Giasone scrive:
      10 febbraio 2009 alle 13:57

      Ciao Paride devi importare il modulo string aggiungendo sotto gli altri “import string”. Infatti nell’errore lo script non riesce a trovare le funzioni strip e atoi del modulo string.

      Replica
  5. Mauro scrive:
    8 dicembre 2009 alle 15:04

    uhm

    Replica
  6. Mauro scrive:
    8 dicembre 2009 alle 15:06

    Avevo scritto un bel commento ma non me l’ha pubblicato…va beh.
    In sintesi, aggiungere:
    import string, errno, signal

    Poi, dopo: #!/usr/bin/env python
    aggiungere: # -*- coding: cp1252 -*-
    per non aver problemi con le vocali accentate.

    Replica
    • quadrispro scrive:
      22 dicembre 2009 alle 09:39

      Forse è finito nello spam, non saprei :/

      Replica
  7. Mauro scrive:
    8 dicembre 2009 alle 15:08

    Manca anche l’invocazione del metodo Hello. Verso la fine dello script,

    try:
    bus_name = dbus.service.BusName(‘org.test.daemon’, bus=session_bus)
    object = Demone(bus_name)
    object.Hello() #<—Aggiungere
    except Exception, e:
    print "Errore ", e

    Replica
  8. Mauro scrive:
    8 dicembre 2009 alle 15:09

    A parte queste sviste, ottimo articolo ;)

    Replica
    • quadrispro scrive:
      22 dicembre 2009 alle 09:39

      Grazie, sistemerò appena possibile! :)

      Replica

Leave a Reply

Fare clic per cancellare la replica.

Chi sono

Se volete contattarmi, potete utilizzare l'apposito form.

Powered by Netsons

Google Friend Connect

Argomenti

Acer Aspire One Aggiornamento Ambienti di sviluppo Arte,libri & Cultura Audio Bug e problemi vari C Compiz Fusion Comunità internazionale Comunità Italiana Cose varie Debian Documentazione e wiki Esperienze Feisty Firefox GNOME GTK+ GTK Rm3WiFi Authenticator Guide e howto Gutsy Hardy Iniziative Installation Report Generator Installazione e problemi all'avvio Intrepid Jaunty Java Karmic Kernel e moduli Last.fm Linpus Lucid MSI U90 Musica Netbook Remix Notizie ONDA MT503HSA Open source & free software Planet Ubuntu-it Programmazione e sviluppo Python Siti,servizi & blog Società e media Traduzioni Ubuntu Ubuntu Developer Summit Università e scienza Viaggi Video Windows Wordpress Xfce Xubuntu

Ultimi commenti

  • Luciano Krostag su Lo sviluppo di Installation Report Generator, il merge di gtk2hs, Universe Contributor Application, e….
  • Pixel su Cinque fantastici ricordi di Londra
  • quadrispro su Ubuntu aiuta a diffondere i virus per Windows
  • gigi su Ubuntu aiuta a diffondere i virus per Windows
  • DarkJackAho su Windows 7, un silenzioso terrorista

Tutti gli articoli

RSS Planet di Ubuntu-it

  • Aldo Latino (aldolat): Presentazione di Haiku
  • Divilinux: Linux-2.6.33 – Nvidia driver e brightness Samsung R610
  • Dario Cavedon: Il Governo stanzia i fondi per la "banda larga". Ma li usa per una nave.
  • Dario Cavedon: L'ultimo pandoro di primavera
  • Alessio Treglia (quadrispro): Cinque fantastici ricordi di Londra

Blogroll

  • (LS) Lorenzo Sfarra
  • Antonio Doldo Linux Blog
  • Bubuntu
  • crisis
  • Dagospia
  • Divilinux Lost Blog
  • Embrace’s Blog
  • Fabrizio Balliano
  • Fradeve OpenBlog
  • Il blog di Totò
  • Il mondo di Paolettopn
  • Leo
  • Maurizio Moriconi
  • Milo Casagrande
  • Palla's Home
  • Parzialmente scremato
  • sottovoce per non svegliare il cane
  • TuxLinux
  • Ubuntu block notes

Accedi o registrati

  • Registrati
  • Collegati
  • Voce RSS
  • RSS dei commenti
  • WordPress.org
rss Commenti RSS valid xhtml 1.1 design by jide powered by Wordpress get firefox