MokaByte Numero 24  -  Novembre 98
Java e i servizi NT
di 
Antonio Cisternino
Per rendere automatica l'esecuzione di una applicazione al login su WinNT si deve attivare un servizio. Vediamo come questo sia possibile per una applicazione Java


 


In questo articolo mi occuperò di mostrare come sia possibile tramutare in servizio NT un’applicazione Java standalone senza far ricorso alle estensioni del JDK di Microsoft.


Introduzione

In questo articolo affronterò un aspetto importante dello sviluppo di server in Java: la possibilità di renderli dei servizi su Windows NT. Descriverò una soluzione shareware offerta da IQX e successivamente una soluzione basata su un sorgente C da me modificato e liberamente utilizzabile.
In tutto l’articolo Java sarà un po’ a margine poiché il servizio vero e proprio è realizzato in C.  L’obiettivo dell’articolo non è comunque quello di descrivere in modo esauriente lo sviluppo di un servizio per Windows NT, bensì quello di illustrare le problematiche dei servizi di NT relative all’attivazione di un server scritto in Java.
Un’interessante evoluzione del codice qui presentato potrebbe essere una libreria JNI che permetta di amministrare il servizio direttamente da Java utilizzando le funzioni presentate.
 

Perché usare un servizio NT?

Chiunque sia abituato a lavorare su Unix o Linux si stupisce nello scoprire che su Windows NT non è disponibile un meccanismo simile al nohup. Contrariamente a quanto accade su Unix infatti in Windows NT quando un utente si disconnette il sistema si preoccupa di terminare tutti i processi di quell’utente impedendo quindi di lasciare un server attivo. Per ottenere lo stesso risultato del demone Unix su Windows NT è necessario scrivere un programma oppure usarne uno già scritto che semplicemente permette di parametrizzare l’applicazione da lanciare. La funzione di questo programma è di registrare, usando opportune chiamate di sistema, un’applicazione come servizio. Una volta installato un servizio può essere avviato o arrestato tramite l’apposito applet nel pannello di controllo di Windows. Il Service Control Manager (SCM) si occupa di amministrare i servizi utilizzando un apposito database in cui memorizza le informazioni relative ad ogni servizio correntemente disponibile nel sistema. Per poter aggiungere un nuovo servizio si chiamano apposite API che ne permettono la registrazione.
 

Service Runner

Il service runner è un’applicazione shareware sviluppata da IQX che si propone appunto come obiettivo quello di trasformare in servizio NT qualsiasi eseguibile per Windows. Questa applicazione, disponibile all’URL http://www.iqx.com/corporate/, può essere utilizzata liberamente per fini non commerciali. Descriverò innanzitutto il suo funzionamento e successivamente, una volta introdotto il codice valuteremo come può essere implementato lo stesso applicativo partendo da quanto sviluppato.La prima caratteristica di service runner è quella di funzionare come processo generatore di altri processi che sono gli applicativi che vanno realmente eseguiti come servizio. In pratica è un sistema per avere una specie di comando che permetta di lasciare un processo in background anche dopo il logoff.
Il servizio installato è quindi service runner ed è il server che avvierà i vari processi gestendoli opportunamente. Per installare il servizio sarà sufficiente eseguire il comando

ServiceRunner –install
Successivamente tramite il pannello di controllo oppure il prompt del DOS usando il comando
net start ServiceRunner
si può far partire il servizio. Una volta avviato il servizio è sufficiente utilizzare il comando
ServiceLaunch <executable> <arg1> <arg2> ...
dove <executable> è il nome del comando da eseguire e <arg1>, <arg2>, … sono gli argomenti della riga di comando da passare all’applicazione.
Per lanciare un server Java utilizzando questo strumento è sufficiente creare un file batch che contenga i comandi necessari all’avvio del server preceduti dall’impostazione delle variabili d’ambiente e, chiamato server.bat questo comando, basta eseguire il comando:
ServiceLaunch server.bat
La necessità di impostare le variabili d’ambiente deriva dal fatto che un servizio non gira solitamente come processo dell’utente che lo ha lanciato bensì come utente di un account particolare dedicato all’esecuzione dei servizi. Capita quindi che le impostazioni dell’ambiente relativi all’utente non siano disponibili nell’account System utilizzato per eseguire il servizio.
Ci sono due limiti fondamentali nell’approccio basato su questo applicativo: il primo è chiaramente legato alla necessità di poter controllare ogni singolo servizio piuttosto che un gruppo di servizi. In questo caso il service runner, controllabile dal pannello di controllo, controlla un numero arbitrario di servizi. Questo indebolisce alquanto il controllo sulle applicazioni in esecuzione; anche perché neanche l’amministratore ha diritto di terminare i processi appartenenti ad un servizio e quindi il pannello di controllo diviene l’unico modo accettabile per controllare questi processi.
Il secondo limite è invece legato alla sicurezza: chiunque può lanciare servizi. Per ridurre i rischi la stessa IQX suggerisce di far partire il service runner usando un altro account piuttosto che quello di sistema System assegnato per default. Questo limita i potenziali danni ma non risolve completamente il problema del controllo dei servizi.
 
 
 
 
 

Il nostro servizio

Una volta descritta un’applicazione commerciale vediamo come sia possibile scrivere un servizio per Windows NT utilizzando il linguaggio C e la Win32. Il codice che descriverò è alla terza revisione: il codice di base è disponibile tra gli esempi forniti con l’SDK di Microsoft, successivamente un certo Dean Troyer lo ha esteso rendendolo parametrico ed infine io l’ho ulteriormente ritoccato aggiungendo funzionalità minori.
Trovo che questo programma sia di una semplicità disarmante, data la complessità dell’operazione, e soprattutto che sia ancora parametrizzabile per realizzare un’applicazione che offra le stesse funzionalità di service runner risolvendo entrambi i problemi elencati. D’altra parte è ridicolo che in un sistema operativo di rete sia così complicato mettere in esecuzione dei processi in background: capita spesso di aver necessità di lasciare un processo a lavorare, anche per giorni, per non parlare poi dei server custom che spesso si sviluppano per far pronte alle proprie esigenze.
Se il nome dell’eseguibile del nostro servizio è service.exe allora il comando

 
service –install

registra il servizio presso il SCM; il comando
service –remove

elimina il servizio dal database dei servizi; il comando
service –debug

esegue il servizio come una normale applicazione per vedere se tutto funziona.

La funzione main del programma in questione si comporta solo come dispatcher di servizio e, dopo aver deciso in base ai parametri passati sulla riga di comando, chiama una funzione apposita. Prenderemo successivamente in considerazione l’azione intrapresa nel caso in cui nessuno switch sia stato specificato o riconosciuto.
Le funzioni associate ai relativi switch sono: CmdInstallService, CmdRemoveService, CmdHelp e CmdDebugService. Ovviamente le due funzioni interessanti da prendere in considerazione sono le prime due poiché mostrano come sia possibile registrare e eliminare un servizio presso il SCM. Per comprendere il funzionamento di base dei servizi resterà infine da analizzare l’avvio e l’arresto di un servizio.
Come ho già detto il modulo di NT responsabile della gestione dei servizi è il Service Control Manager, per poter operare con questo modulo è necessario collegarsi utilizzando la chiamata di sistema

OpenSCManager(...)
Una volta ottenuta una handle al manager dei servizi è possibile installare un servizio usando la chiamata di sistema
CreateService(...)
La creazione del servizio comporta, da parte dell’SCM, la registrazione dell’eseguibile che ha eseguito la chiamata di sistema presso il database dei servizi. All’atto della creazione del servizio vengono poi specificate alcune informazioni che il SCM userà per fornire informazioni all’utente tramite l’applet dei servizi nel pannello di controllo.
Una volta creato il servizio e ottenuta una handle allo stesso viene chiamata una funzione, da definire, chiamata ServiceInstall in cui eseguire particolari task di installazione dipendenti dal servizio. Quando questa funzione termina viene chiusa la handle restituita dalla CreateService utilizzando la funzione
 
    CloseServiceHandle(...)
L’eliminazione di un servizio segue, in linea di principio, lo stesso schema dell’installazione: si ottiene una handle al SCM e successivamente una handle al servizio utilizzando la chiamata di sistema
    OpenService(...)
infine si utilizza la handle così ottenuta per invocare la chiamata
    DeleteService(...)
che elimina il servizio dal database del SCM. C’è un’unica anche se non banale differenza tra la funzione che installa il servizio e quella che lo elimina: quando viene richiesta la rimozione il servizio potrebbe essere attivo e va quindi arrestato prima di procedere all’eliminazione. Per questo motivo, una volta ottenuta una handle al servizio si usa la funzione
 
    ControlService(...)
per richiedere l’arresto del servizio e si attende, con un ciclo di polling che questo sia effettivamente terminato controllando lo stato ogni secondo usando la funzione apposita
    QueryServiceStatus(...)
Una volta arrestato il servizio si può procedere all’eliminazione usando la chiamata DeleteService.
Le funzioni elencate sono quelle necessarie ad interagire col Service Control Manager per aggiungere ed eliminare un servizio. Come avevo già detto in precedenza non sono entrato nel dettaglio dei parametri utilizzati poiché non è scopo di questo articolo quello di approfondire l’aspetto dei servizi per Windows NT.
La funzione CmdHelp si occupa solamente di stampare sulla console l’help dell’applicazione mentre la funzione CmdDebug si occupa di far partire il servizio come un’applicazione normale per provare il corretto funzionamento.
Resta ora da capire come funziona l’avvio e l’arresto di un servizio dal punto di vista della gestione del codice. Se la funzione main non riconosce qualche comando particolare sulla riga di comando assume di essere stata invocata dal SCM e quindi avvia il servizio vero e proprio.
Per avviare un servizio bisogna associare al servizio il thread corrente; per poter fare questo è sufficiente effettuare una chiamata alla funzione di sistema
    StartServiceCtrlDispatcher
A questa funzione viene passata una tabella di servizi, che in questo caso contiene una sola riga, in cui viene associato al nome del servizio la funzione che verrà invocata come punto di inizio dell’esecuzione del servizio. Nel nostro caso questa funzione è la funzione service_main.
A questo punto il controllo del flusso passa alla funzione service_main che si occupa di registrare il gestore degli eventi e segnalare e di eseguire il servizio vero e proprio.
La registrazione dell’handler degli eventi per il servizio è importante poiché permette di associare al servizio una funzione di gestione degli eventi, come ad esempio la richiesta di arrestare un servizio.
Una volta effettuata l’inizializzazione nel service_main si trova la chiamata alla funzione ServiceStart il cui compito è appunto quello di eseguire il servizio vero e proprio.
Da citare ancora è il metodo ServiceStop invocato prima di interrompere il servizio.
 

Java, IQX e i servizi NT

Sfruttando il codice allegato all’articolo è possibile ridefinire le funzioni ServiceStart, ServiceInstall, ServiceRemove e ServiceStop, chiamate opportunamente dal codice C che si interfaccia realmente a Service Control Manager.
Se la StartService si occupa di lanciare un processo e poi di aspettarne la terminazione si comporta correttamente secondo il significato del metodo in questione. Per Java è quindi necessario assicurarsi che le variabili d’ambiente siano definite per l’utente System, successivamente, mediante una chiamata alla funzione
    CreateProcess(...)
 

È possibile lanciare la JVM che comincia ad assolvere il servizio stesso. Per impedire però che la funzione ServiceStart termini, e quindi che il resto del codice consideri terminato il servizio, è necessario ricorrere alla funzione
    WaitForSingleObject(...)
 

Che sospende l’esecuzione del codice corrente finché il processo creato non è morto.
Nel caso di IQX il programma ServiceRunner è semplicemente un servizio come quello illustrato in questo articolo ed è capace di eseguire più processi contemporaneamente. In questo caso non è però possibile distinguere tra i vari servizi che si attivano mentre utilizzando il codice presentato in questo articolo è possibile controllare ogni applicazione come singolo servizio.
 

Conclusioni

In questo articolo ho presentato la struttura C di un servizio per Windows NT. Ho mostrato che esistono alternative commerciali e che sono tutti più o meno uguali a quello presentato.
Abbiamo visto quali funzioni della API 32 di Windows sono necessarie per interagire con il SCM.
Infine abbiamo discusso su come sia possibile eseguire Java come Servizio.
 

Allegato

Per concludere ecco i sorgenti con cui mettere in pratica quanto detto fino ad ora.
 
 
 
 
 


MokaByte Web  1998 - www.mokabyte.it 
MokaByte ricerca nuovi collaboratori. 
Chi volesse mettersi in contatto con noi può farlo scrivendo a mokainfo@mokabyte.it