Servizi in C# su macchine Linux con Mono

di Andrea Zani, in Mono,

Pochi giorni fa ho dovuto rifarmi una macchina su cui girava OpenSuse (la distribuzione che preferisco), ed ho deciso di installare la nuova versione di Ubuntu 8.04 per curiosità visto che l'ultima versione provata di Ubuntu era la 7.04 e volevo provare con mano l'evoluzione avvenuta in questi dodici mesi.

La prima delusione l'ho avuta nel constatare che la versione presente nei repository di Mono è la 1.2.6, mentre l'ultima release è la 1.9.1. Non mi pongo problemi, e visto che dovevo installare un servizio per... ehi, un momento, non sono obbligato a scrivere nel dettaglio i fatti miei! Riprendo: dovendo installare un servizio scritto in C# su questa macchina installo mono e mono-service2 dai repository di Ubuntu. mono-service2 è un tool che permette la gestione dei servizi scritti per Mono sotto Linux.

Per informazione riporto un pubblico esempio di un servizio scritto in C# che può girare sia sotto Windows che Linux. Il codice seguente è possibile compilarlo Visual Studio, da linea di comando sotto Linux con Mono o con MonoDevelop, e può essere testato anche come console application:

// Main.cs created with MonoDevelop
// User: az

using System;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.ServiceProcess;
using System.IO;
using System.Configuration.Install;
using System.Threading;

namespace service1
{
 class MainClass
 {
  private const string LogFile = "TestService.log";

  // The main entry point for the process
  static void Main()
  {
   Write2Log(null, "Main starting");
   System.ServiceProcess.ServiceBase[] ServicesToRun;
   ServicesToRun = new System.ServiceProcess.ServiceBase[] { new TestService("TestService1")};
   System.ServiceProcess.ServiceBase.Run(ServicesToRun);
   Write2Log(null, "Main ended");
  }

  public static void Write2Log(string category, string message)
  {
   using (StreamWriter w = new StreamWriter(LogFile, true, System.Text.Encoding.UTF8) )
   {
    if ( category != null )
     w.WriteLine(DateTime.Now.ToString() + " " + category + " - " + message);
    else
     w.WriteLine(DateTime.Now.ToString() + " " + message);
   }
  }
 }

 public class TestService : System.ServiceProcess.ServiceBase
 {
  private Thread mainThread = null;
  private bool stopThread = false;

  public TestService(string name)
  {
   this.ServiceName = name;
   this.CanPauseAndContinue = true;
  }

  protected override void Dispose( bool disposing )
  {
   MainClass.Write2Log(ServiceName, "Dispose");
   base.Dispose( disposing );
  }

  protected override void OnStart(string[] args)
  {
   MainClass.Write2Log(ServiceName, "Starting Service...");   
   mainThread = new Thread (new ThreadStart (_MainLoop));
   stopThread = false;
   mainThread.Start ();
  }

  protected override void OnStop()
  {
   MainClass.Write2Log(ServiceName, "Stopping Service...");
   if (mainThread != null) {
    stopThread = true;
    mainThread.Join ();
    mainThread = null;
   }
  }

  protected override void OnPause()
  {
   MainClass.Write2Log(ServiceName, "Pausing Service...");
  }

  protected override void OnContinue()
  {
   MainClass.Write2Log(ServiceName, "Continuing Service...");
  }

  private void _MainLoop ()
  {
   while (!stopThread)
    Thread.Sleep (2000);
  }
 }

 /// <summary />
 /// This class allows easy installation of this service on Windows.
 /// </summary />
 [RunInstaller(true)]
 public class ProjectInstaller : System.Configuration.Install.Installer
 {
  private System.ServiceProcess.ServiceProcessInstaller serviceProcessInstaller1;
  private System.ServiceProcess.ServiceInstaller serviceInstaller1;
  private System.ComponentModel.Container components = null;

  public ProjectInstaller()
  {
   // This call is required by the Designer.
   InitializeComponent();
  }

  /// <summary />
  /// Clean up any resources being used.
  /// </summary />
  protected override void Dispose( bool disposing )
  {
   if( disposing )
   {
    if(components != null)
    {
     components.Dispose();
    }
   }
   base.Dispose( disposing );
  }

  #region Component Designer generated code
  /// <summary />
  /// Required method for Designer support - do not modify
  /// the contents of this method with the code editor.
  /// </summary />
  private void InitializeComponent()
  {
   this.serviceProcessInstaller1 = new System.ServiceProcess.ServiceProcessInstaller();
   this.serviceInstaller1 = new System.ServiceProcess.ServiceInstaller();
   //this.serviceInstaller2 = new System.ServiceProcess.ServiceInstaller();
   // 
   // serviceProcessInstaller1
   // 
   this.serviceProcessInstaller1.Password = null;
   this.serviceProcessInstaller1.Username = null;
   // 
   // serviceInstaller1
   // 
   this.serviceInstaller1.DisplayName = "Test Service 1";
   this.serviceInstaller1.ServiceName = "TestService1";
   // 
   // ProjectInstaller
   // 
   this.Installers.AddRange(new System.Configuration.Install.Installer[] {
       this.serviceProcessInstaller1,
       this.serviceInstaller1
     });
  }
  #endregion
 }
}

Questo servizio è solo un test: scrive in un file di testo quando il servizio viene avviato e quando viene fermato. Una volta testato e confermato il suo funzionamento corretto, provo ad installarlo come servizio sotto Ubuntu con mono-service2. L'help di questo comando è molto chiaro:

az:~$ mono-service2 
You must specify at least the assembly name

Usage is: /usr/bin/mono-service2 [options] service

    -d:<directory />         Working directory
    -l:<lock />         Lock file (default is /tmp/<service />.lock)
    -m:<syslog />       Name to show in syslog
    -n:<service />      Name of service to start (default is first defined)
    --debug                Do not send to background nor redirect input/output
    --no-daemon            Do not send to background nor redirect input/output

Controlling the service:

    kill -USR1 `cat <lock />`    Pausing service
    kill -USR2 `cat <lock />`    Continuing service
    kill `cat <lock />`          Ending service

az:~$ 

Ok, vado nella directory dove è presente il codice compilato del servizio e lancio il comando con l'opzione della working directory:

sudo mono-service2 -d:/home/az/project/service1/service1/bin/Debug/  /home/az/project/service1/service1/bin/Debug/service1.exe

Ma ottengo un non precisato errore: "41: Syntax error: Bad fd number". Una veloce ricerca e trovo che è un bug noto sotto Ubuntu. Sempre a quel link è presente la correzione da fare, anche se un modo migliore per risolvere ogni problema lo spiegherò dopo.

Finalmente tutto funziona. Ora per fare le cose per bene dobbiamo fare in modo che tale servizio sia avviato in automatico all'avvio della macchina, senza nessuna autenticazione dell'utente. Per ottenere questo è necessario creare uno script apposito in "/etc/init.d/". Lo script, che chiamerò "azservice" è il seguente:

#!/bin/sh
# Start/stop the az daemon.
#
### BEGIN INIT INFO
# Provides:          service1
# Short-Description: Test di AZ
# Description:       Service write in C# for mono and linux world
### END INIT INFO

. /lib/lsb/init-functions

case "$1" in
start) log_daemon_msg "Starting test service az" "az-service1"
 mono-service2 -d:/home/az/project/service1/service1/bin/Debug/  /home/az/project/service1/service1/bin/Debug/service1.exe
 ;;
stop) log_daemon_msg "Stopping test service az" "az-service1"
 kill `cat /tmp/service1.exe.lock`
        ;;
restart) log_daemon_msg "Restarting test service az" "az-service1"
 kill `cat /tmp/service1.exe.lock`
 mono-service2 -d:/home/az/project/service1/service1/bin/Debug/  /home/az/project/service1/service1/bin/Debug/service1.exe
        ;;
reload|force-reload) log_daemon_msg "Reloading test service az" "az-service1"
 kill `cat /tmp/service1.exe.lock`
 mono-service2 -d:/home/az/project/service1/service1/bin/Debug/  /home/az/project/service1/service1/bin/Debug/service1.exe
        ;;
*) log_action_msg "Usage: /etc/init.d/azservice {start|stop|restart|reload|force-reload}"
        exit 2
        ;;
esac
exit 0

Dipendentemente dall'opzione passata (start, stop, restart, reload, force-reload), viene invocato in comando mono-service2 con le opzioni necessarie per avviare, fermare o riavviare il servizio:

sudo /etc/init.d/azservice start
sudo /etc/init.d/azservice stop

Per essere richiamato con il precedente modo dobbiamo aggiungere a questo file il flag "avviabile":

sudo chmod +x  /etc/init.d/azservice

Trovo interessante il modo utilizzato per fermare un servizio. Prendendo la linea di codice utilizzata si può notare una strana sintassi per chi non è avvezzo a Linux:

kill `cat /tmp/service1.exe.lock`

cat /tmp/service1.exe.lock visualizza normalmente a video il contenuto del file (in questo caso il numero di processo del servizio). Racchiuso tra quegli apici, prende l'output del comando e lo invia a kill che fermerà il processo del servizio.

Per completare il tutto dobbiamo inviare un ultimo comando. Linux ha vari level di avvio. Ognuno di essi si contraddistingue per l'avvio di n servizi. Maggiori informazioni a riguardo si possono trovare qui. Per fare in modo che il nostro servizio sia collegato a questi level, dobbiamo scrivere il comando:

sudo update-rc.d azservice defaults

Dove azservice è il nome del file che abbiamo creato in /etc/init.d/. Il nostro lavoro è finito. Ad ogni avvio il servizio verrà richiamato. Un piccolo avvertimento: questo servizio ha normalmente accesso a tutti i file e un'operazione sbagliata può creare danni irreversibili sulla macchina (ipottizzando che debba cancellare dei file presenti in una directory, un comando come rm -rf * può avere effetti indesiderati se richiamata nella directory root della macchina ;)).

Ubuntu presenta un tool "Servizi" che permette la gestione dei servizi sulla macchina. Si può notare come quello appena creato non sia presente nella lista. Non volendo investigare oltre per scoprire il perché, ho utilizzato di un altro strumento più potente, "BUM" (Boot-up manager) installabile dai repository, in grado di gestire anche quello che ho appena creato, come si vede in figura:

boot-up manager

Nel caso di Ubuntu consiglio anche l'upgrade immediato all'ultima versione 1.9.1 di Mono. Per ottenere questo è sufficiente aggiungere il repository di Mono, come è spiegato qui, che risolve anche il bug prima citato di mono-service2.

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Nella stessa categoria
I più letti del mese