UNIVERSITÀ DEGLI STUDI DI BOLOGNA FACOLTÀ DI INGEGNERIA Corso di Laurea in Ingegneria Informatica Progetto per il corso “Reti di calcolatori LS” tenuto dal Prof. Antonio Corradi Sistema distribuito per il controllo remoto di Software SCADA HMI Paolo di Francia Anno Accademico 2005 Indice Introduzione………………………………………..………………3 Teconologie utilizzate………………………………………...……………...3 o Remoting .NET…………………………………………...…………...3 o Oggetti remoti…………………………………………...…………….4 o Canali……………………………………………………...…………..5 Canale TCP o Politiche di gestione………………………………………………..…5 o Esempio di remoting con un canale TCP……………………………..7 Architettura dell’ applicazione……………………………………………....9 Implementazione dell’ algoritmo two phase lock………………………….10 Aspetti di sincronizzazione………………………………………………...13 Request Engine…………………………………………………………….14 Response Engine…………………………………………………………...16 Politiche di scheduling delle richieste……………………………………...17 Gestioni Errori……………………………………………………………...17 Conclusioni………………………………………………………………...18 Bibliografia………………………………………………………………...18 Introduzione Il progetto si pone l’obiettivo di sviluppare mediante costrutti messi a disposizione dalla piattaforma .NET un’applicazione distribuita che consenta di interagire con un software SCADA commerciale in particolare Citect.L’applicazione sarà composta da un’entità Server e più entità Client che attraverso un oggetto condiviso mediante il Remoting.NET possano inviare e ricevere istruzioni al software SCADA.L’applicazione Server deve fornire l’istanza principale dell’oggetto condiviso e occuparsi della gestione di tutte le richieste che vengono inviate dai Client, a sua volta dovrà gestire le richieste pendenti comunicando verso Citect e rendendo disponibili i risultati delle istruzioni a tutti i Client connessi.L’entità Server avrà la possibilità di inviare messaggi con informazioni di stato mediante modalità broadcast ai Client attualmente connessi. Il Server avrà a disposizione un archivio dati in cui verranno inserite le richieste e le relative risposte in modo che in caso di temporanea inabilitazione del Server queste possano poi essere processate in tempi successivi.Le richieste verranno organizzate in modo da mantenere le indicazioni temporali e l’identificativo del Client richiedente.Le richieste saranno processate con politica di tipo Fifo.Entrambe le entità potranno esportare i dati relativi a richieste e risposte su file xml.Il Server mantiene una lista degli identificativi dei Client connessi e sarà in grado di visualizzare le risorse di sistema attualmente utilizzate. Al fine di consentire una corretta sincronizzazione tra richieste e risposte verranno utilizzati un Request Engine e un Response Engine in modo da accedere all’archivio dati condiviso. L’interfaccia verso Citect verrà sviluppata attraverso l’implementazione di un oggetto con tecnologia Microsoft COM. Tecnologie utilizzate Remoting .NET .NET remoting fornisce una struttura che consente agli oggetti di interagire fra loro su domini di applicazione. La struttura fornisce una serie di servizi, tra cui supporto di attivazione e durata, oltre a canali di comunicazione che si occupano del trasporto di messaggi verso e da applicazioni remote. I formattatori vengono utilizzati per la codifica e decodifica dei messaggi, prima che il canale li trasmetta. Le applicazioni possono utilizzare la codifica binaria in caso di prestazioni critiche, oppure la codifica XML laddove è essenziale l'interoperabilità con altre strutture remote. La gestione della durata degli oggetti remoti senza un supporto della struttura sottostante non è sempre facile. 3 . Fig.1 .NET remoting mette a disposizione una serie di modelli di durata tra cui scegliere, suddivisi in due categorie: Oggetti attivati da client Oggetti attivati da server Gli oggetti attivati da client sono controllati da un programma di gestione durata basato su lease, che assicura che l'oggetto venga sottoposto a garbage collection alla scadenza del lease. Nel caso di oggetti attivati da server, gli sviluppatori possono selezionare un modello "single call" o "singleton". Oggetti remoti Uno degli obiettivi principali di una struttura di remoting è fornire l'infrastruttura in grado di nascondere le complessità dei metodi di chiamata per oggetti remoti e riportarne i risultati. Qualsiasi oggetto esterno all' application domain del mittente dovrebbe essere considerato remoto, anche se gli oggetti vengono eseguiti sullo stesso computer. All'interno dell' application domain, tutti gli oggetti vengono passati in base al riferimento mentre i tipi di dati primitivi in base al valore. Poiché i riferimenti a oggetti locali sono validi solo all'interno dell'application domain in cui vengono creati, non è possibile spostarli o riportarli dalle chiamate remote di metodi in questa forma. Tutti gli oggetti locali che devono attraversare il confine dell' application domain devono passare in base al valore e dovrebbero essere contrassegnati dall'attributo personalizzabile [serializable], oppure devono implementare l'interfaccia ISerializable. È possibile modificare qualsiasi oggetto in un oggetto remoto facendolo derivare da MarshalByRefObject. Quando un client attiva un oggetto remoto, riceve un proxy per questo oggetto. Tutte le operazioni su questo proxy vengono indirizzate in modo da consentire all'infrastruttura remota di intercettare e inoltrare correttamente le chiamate. Questa operazione di indirizzamento influisce sulle prestazioni, ma il compilatore JIT ed EE (execution engine) sono stati ottimizzati per evitare inutili penalizzazioni delle prestazioni, quando gli oggetti proxy e remoti si trovano nello stesso application domain. Nei casi in cui invece questi oggetti si trovano in domini di applicazione diversi, tutti i parametri di chiamata del metodo nello stack vengono convertiti in messaggi e spostati nel application domain remoto, dove i messaggi vengono messi nuovamente in un frame dello stack e il 4 metodo viene richiamato. La stessa procedura viene utilizzata per riportare i risultati della chiamata del metodo. Canali I canali vengono utilizzati per trasmettere messaggi verso e da oggetti remoti. Quando un client richiama un metodo su un oggetto remoto, i parametri e altri dettagli relativi alla chiamata vengono trasmessi all'oggetto remoto attraverso il canale. I risultati della chiamata tornano poi al client seguendo la stessa procedura. Un client è in grado di selezionare qualsiasi canale registrato sul "server" per comunicare con l'oggetto remoto. In questo modo gli sviluppatori possono selezionare i canali più adatti alle proprie esigenze. È possibile inoltre personalizzare tutti i canali esistenti o creare nuovi canali che utilizzino protocolli di comunicazione diversi. La scelta dei canali avviene secondo le regole descritte di seguito: È necessario registrare almeno un canale con la struttura di remoting prima di poter richiamare un oggetto remoto. I canali devono essere registrati prima degli oggetti. I canali vengono registrati per application domain. È possibile avere diversi domini di applicazione in un solo processo. Quando un processo termina, tutti i canali registrati vengono automaticamente distrutti. Non è consentito registrare lo stesso canale in ascolto su una stessa porta più di una volta. Anche se i canali vengono registrati per application domain, più domini di applicazione sullo stesso computer non possono registrare lo stesso canale in ascolto sulla stessa porta. I client possono comunicare con un oggetto remoto utilizzando qualsiasi canale registrato. La struttura di remoting assicura che l'oggetto remoto sia collegato al canale adatto quando un client tenta di stabilire una connessione ad esso. Il client si occupa di richiamare RegisterChannel sulla classe ChannelService prima di provare a comunicare con un oggetto remoto. Canale TCP Il canale TCP utilizza un formattatore binario per serializzare i messaggi in un flusso binario e trasportare il flusso all'URI di destinazione tramite il protocollo TCP. Politiche di gestione La struttura di remoting supporta l'attivazione da server e client di oggetti remoti. L'attivazione da server si utilizza in genere quando gli oggetti remoti non sono necessari per la conservazione di uno stato tra le chiamate del metodo; si utilizza anche nei casi in cui più client richiamano metodi sulla stessa istanza di oggetto e l'oggetto conserva lo stato tra le chiamate delle funzioni. Allo stesso tempo, gli oggetti attivati da client vengono istanziati dal client, che gestisce la durata dell'oggetto remoto utilizzando un sistema basato su lease fornito a tale scopo. Le informazioni riportate di seguito sono necessarie per la registrazione di un oggetto remoto con la struttura: Il nome del tipo di oggetto remoto. L'oggetto URI che i client utilizzeranno per individuare l'oggetto. 5 La modalità oggetto richiesta per l'attivazione da server, che può essere SingleCall o Singleton. Un oggetto SingleCall crea un'istanza di classe per ogni chiamata del client, anche se le chiamate provengono dallo stesso client. L'invocazione successiva, sarà sempre servita da una differente istanza del server anche se la precedente non è stata ancora riciclata dal sistema. Un oggetto Singleton invece non presenta mai più di una istanza contemporaneamente. Se una istanza è già esistente la richiesta viene soddisfatta da quella istanza. Se l'istanza non esiste, alla prima richiesta viene creata dal server e tutte le successive richieste vengono soddisfatte da quella istanza. È possibile registrare un oggetto remoto richiamando RegisterWellKnownType, passando le informazioni riportate sopra come parametri, oppure memorizzarle in un file di configurazione, richiamare ConfigureRemoting e passare il nome del file di configurazione come parametro. Queste due funzioni possono essere entrambe utilizzate per registrare oggetti remoti perché svolgono esattamente la stessa operazione. La seconda è più adatta perché il contenuto del file di configurazione può essere alterato senza dover compilare di nuovo l'applicazione host. Il frammento di codice seguente mostra come registrare la classe HelloService come oggetto remoto SingleCall. RemotingServices.RegisterWellKnownType( "server", "Samples.HelloServer", "SayHello", WellKnownObjectMode.SingleCall); In questo caso "server" è il nome del assembly, HelloServer è il nome della classe e SayHello è l'oggetto URI Quando l'oggetto viene registrato, la struttura crea un riferimento per questo oggetto remoto ed estrae i metadati necessari per l'oggetto dall'assembly. Queste informazioni, insieme all'URI e al nome dell'assembly, vengono poi memorizzate nel riferimento dell'oggetto all'interno di una tabella della struttura di remoting, utilizzata per la registrazione degli oggetti remoti registrati. Si noti che l'oggetto remoto stesso non viene istanziato dal processo di registrazione, ma solo quando un client tenta di richiamare un metodo sull'oggetto oppure attiva l'oggetto dal lato client. A questo punto, qualsiasi client che conosca l'URI di questo oggetto può ottenere un proxy registrando il canale preferito con ChannelServices e attivare l'oggetto richiamando new, GetObject o CreateInstance. Il frammento di codice seguente costituisce un esempio di questa operazione: ChannelServices.RegisterChannel(new TCPChannel); HelloServer obj = (HelloServer)Activator.GetObject( typeof(Samples.HelloServer), "tcp://localhost:8085/SayHello"); In questo caso "tcp://localhost:8085/SayHello" specifica che l'utente intende stabilire una connessione all'oggetto remoto nell'endpoint SayHello utilizzando TCP sulla porta 8085. È possibile utilizzare GetObject o new per l'attivazione del server. L'oggetto non viene 6 istanziato quando viene effettuata una di queste chiamate. In pratica non viene generata alcuna chiamata di rete. La struttura ottiene le informazioni sufficienti dai metadati per creare il proxy senza connettersi affatto all'oggetto remoto. Viene solo stabilita una connessione di rete quando il client richiama un metodo sul proxy. Quando la chiamata arriva al server, la struttura estrae l'URI dal messaggio, esamina le tabelle della struttura di remoting per individuare il riferimento per l'oggetto corrispondente all'URI, quindi istanzia l'oggetto se necessario, inoltrando la chiamata del metodo all'oggetto. Se l'oggetto è registrato come SingleCall, viene eliminato al termine della chiamata del metodo. Per ciascun metodo invocato viene creata una nuova istanza dell'oggetto. L'unica differenza tra GetObject e new è che il primo consente di specificare un URL come parametro, mentre il secondo lo ottiene dalla configurazione. È possibile utilizzare CreateInstance o new per gli oggetti attivati da client. Entrambi consentono di istanziare un oggetto utilizzando i costruttori con dei parametri. La durata degli oggetti attivati da client è controllata dal servizio di leasing, fornito dalla struttura di remoting. Esempio di remoting con un canale TCP Questa appendice indica come scrivere un'applicazione remota semplice "Hello World". Il client passa una stringa all'oggetto remoto, che allega le parole "Hi There" alla stringa e riporta il risultato al client. Questo codice deve essere salvato come server.cs. Di seguito è riportato il codice per il server: using using using using System; System.Runtime.Remoting; System.Runtime.Remoting.Channels; System.Runtime.Remoting.Channels.Tcp; namespace RemotingSamples { public class Sample { public static int Main(string [] args) { // Crea e registra un nuovo canale Tcp TcpChannel chan = new TcpChannel(8085); ChannelServices.RegisterChannel(chan); // Il metodo RegisterWellKnownServiceType permette di registrare l’oggetto per la // futura attivazione [PARAMETRI (Tipo, URI, metodo di attivazione)] RemotingConfiguration.RegisterWellKnownServiceType (Type.GetType("RemotingSamples.HelloServer,object"), "SayHello", WellKnownObjectMode.SingleCall); System.Console.WriteLine("Hit to exit..."); System.Console.ReadLine(); return 0; } } } 7 Questo codice deve essere salvato come client.cs: using using using using System; System.Runtime.Remoting; System.Runtime.Remoting.Channels; System.Runtime.Remoting.Channels.Tcp; namespace RemotingSamples { public class Client { public static int Main(string [] args) { TcpChannel chan = new TcpChannel(); ChannelServices.RegisterChannel(chan); HelloServer obj = (HelloServer)Activator.GetObject(typeof(RemotingSamples.Hello Server) , "tcp://localhost:8085/SayHello"); if (obj == null) System.Console.WriteLine("Could not locate server"); else Console.WriteLine(obj.HelloMethod("Carlo")); return 0; } } } Questo codice deve essere salvato come object.cs: using using using using System; System.Runtime.Remoting; System.Runtime.Remoting.Channels; System.Runtime.Remoting.Channels.Tcp; namespace RemotingSamples { public class HelloServer : MarshalByRefObject { public HelloServer() { Console.WriteLine("HelloServer activated"); } public String HelloMethod(String name) { Console.WriteLine("Hello.HelloMethod : {0}", name); return "Hi there " + name; } } } 8 Architettura dell’ applicazione Il sistema è composto di due entità che interagiscono fra loro al fine di eseguire alcune funzionalità su un software esterno ad esse. Ci si rende quindi conto che le problematiche da affrontare sono legate al corretto funzionamento dell’ applicazione distribuita in se e del software esterno all’applicazione, si è cercato quindi di sviluppare due entità client e server che siano il più possibile disaccoppiate, in modo che se anche il nodo in cui è in esecuzione lo Scada non è attivo, le richieste possano essere mantenute ed eseguite il prima possibile. Fig.2 Al fine di poter gestire in modo sicuro la comunicazione fra la varie parti che compongono il sistema in caso di errori, disservizi e altri inconvegnenti, l’applicazione è strutturata su un archivio dati cioè un database. Tale archivio è composto da due tabelle, una contenente i record delle richieste e una contenente i record delle risposte. Visti i costrutti disponibili per l’interazione con database presenti in ADO.NET tale scelta permette di ricavare in modo rapido e semplice le informazione desiderate. In questo modo una eventuale caduta del server non provoca particolari danni in quanto al successivo riavvio le richieste risiederanno ancora nell’ archivio e potranno di nuovo essere gestite. Al fine di lasciare l’archivio in uno stato sempre consistente i comandi su esso vengono eseguiti attraverso il protocollo Two Phase Lock, tale scelta deriva dalla semplicità di implementazione di una tale strategia attraverso ADO.NET, di seguito viene riportata una breve descrizione di tale protocollo e la relativa implementazione tramite codice. I comandi che si possono eseguire attraverso Client remoti sono: 9 1. 2. 3. lettura di un Tag scrittura di un Tag esecuzione di una funzione CiCode (linguaggio proprietario di Citect) Implementazione dell’ algoritmo two phase lock Two Phase Lock è composto da tre stati: 1. Stato working Durante l’esecuzione del corpo dell’azione atomica. Quando il processo è in questo stato gli oggetti sono inconsistenti. Se l’elaboratore cade, il meccanismo di recupero deve abortire l’azione atomica. 2. Stato committing Durante la terminazione corretta dell’azione.Gli oggetti sono al loro stato finale. Il processo commuta in tale stato tramite la commit. Se l’elaboratore cade il meccanismo di recupero deve completare l’azione (valori finali già disponibili). 3. Stato aborting Durante la terminazione anomala dell’azione atomica.Gli oggetti devono essere ripristinati al loro valore iniziale. Il processo commuta in tale stato tramite abort.Se l’elaboratore cade durante l’azione di aborto,il meccanismo di recupero deve garantire il completamento del ripristino dei valori iniziali degli oggetti. Fig.3 10 L’archivio richieste e risposte deve essere utilizzato in modo da garantire un certo livello di sicurezza sulle operazioni di inserimento e prelievo di record, in particolare se durante una operazione di inserimento si verifica un errore, l’azione deve essere abortita e l’archivio riportato allo stato precedente. ADO .NET semplifica notevolmente la soluzione di inconvegnenti di questo tipo, attraverso la classe OleDBTransaction. Essa dispone di tre metodi che consentono la rapida ed efficace implementazione dell’algoritmo Two Phase Lock, in particolare: 1. BeginTransaction(); Inizia una transazione di database nidificata. 2. Commit(); Esegue il commit della transazione di database. 3. RollBack(); Esegue il rollback di una transazione da uno stato in sospeso. Se il DataBase genera un errore, verrà eseguito il RollBack della transazione e non verrà registrata alcuna modifica. Se tuttavia non viene segnalato alcun errore, sarà possibile eseguire il commit della transazione per consentire l’aggiornamento dei dati. La struttura delle chiamate è riportata di seguito: //Create a connection and open the connection to the database. OleDBConnection conn = new OleDBConnection(sConnString); Conn.Open(); OleDBTransaction trans = conn.BeginTransaction(); try { //Execute SQL commands or other database transaction. : : trans.Commit(); } catch(Exception e) { //Error handling, logging, reporting, and so on : : trans.RollBack(); } finally { //Always close connection conn.Close(); } Nell’ applicazione i due metodi che inseriscono e prelevano richieste e risposte sono implementati nel seguente modo: 11 public void insertRequest(string table, string IP, string RequestTime, string Client, string Kind, string NewValue, string RequestText, OleDbConnection connection ) { string strInsert = "INSERT INTO "+table+"( IP, RequestTime, Client, Kind, NewValue, RequestText )" + " VALUES ( '"+IP+"', '"+RequestTime+"', '"+Client+"', '"+Kind+"', '"+NewValue+"', '"+RequestText+"' )"; OleDbCommand inst = new OleDbCommand(strInsert,connection); OleDbTransaction localTrans = connection.BeginTransaction(IsolationLevel.ReadCommitted); inst.Transaction = localTrans; try { inst.ExecuteNonQuery(); localTrans.Commit(); } catch(OleDbException o) { localTrans.Rollback(); } } public void insertResponse(string table, string IP, string RequestTime, string Client, string Kind, string ResponseText, OleDbConnection connection ) { string strInsert = "INSERT INTO "+table+"( IP, RequestTime, Client, Kind, ResponseText )"+" VALUES ( '"+IP+"', '"+RequestTime+"', '"+Client+"', '"+Kind+"', '"+ResponseText+"' )"; OleDbCommand inst = new OleDbCommand(strInsert,connection) ; OleDbTransaction localTrans = connection.BeginTransaction(IsolationLevel.ReadCommitted); inst.Transaction = localTrans; try { inst.ExecuteNonQuery(); localTrans.Commit(); } catch { localTrans.Rollback(); } } 12 Si noti l’esecuzione dell’azione di commit() al termine del comando SQL, se si verifica qualche errore viene generata un’eccezione che provoca la chiamata al metodo Rollback() Aspetti di sincronizzazione Tutti i client connessi al server utilizzano la stessa istanza dell’oggetto remoto, si rende quindi necessario definire una politica di accesso all’oggetto in modo che le applicazioni distribuite possano condividere la risorsa in modo trasparente per l’utente. Di fatto da client le uniche operazioni che devono essere controllate sono l’aggiunta di una richiesta o il prelievo delle risposte alle chiamate eseguite dal server. Si pone poi un altro problema, il server deve eseguire comandi su un software SCADA e non deve “stressarlo” in modo eccessivo, in quanto esso ha il compito primario di mantenere una moltitudine di informazioni online, e di aggiornarle attraverso letture o scritture su PLC ogni 250 mSec. Da ciò si è pensato di eseguire una singola operazione ad intervalli regolari in modo da non provocare un eccessivo overhead. Va poi detto che Citect non dispone di un meccanismo di schedulazione dei comandi esterni, da ciò si evince che operazioni eseguite in modo concorrente non possono essere gestite da Citect in modo autonomo. La politica implementata per acquisire la risorsa e non renderne possibile l’utilizzo da parte di altri client, avviene attraverso la creazione di un thread ad ogni richiesta, tale thread deve controllare che la risorsa sia disponibile e in tal caso prenderne l’uso esclusivo, aggiungere la richiesta e quindi rilasciare la risorsa. In particolare per semplificare l’accesso all’archivio delle richieste i client si interfacciano con un ArrayList che le contiene in modo cronologicamente ordinato, l’ArrayList assume quindi il comportamento di una coda FIFO, è poi compito del server andare a prelevare tali richieste e inserirle nell’archivio. Il server deve quindi disporre di due meccanismi di accesso all’archivio dati, uno per prelevare le richieste e uno per inserire le risposte. 13 Request Engine Fig.4 E’ costituito da un thread che alla partenza del servizio inizia la propria esecuzione e periodicamente controlla se sono presenti richieste pendenti, in tal caso deve inserirle del database delle richieste, a tal fine deve essere sincronizzato con il meccanismo di inserimento delle risposte (Request Engine), in modo da lasciare l’archivio in uno stato consistente. Come si può notare dal codice riportato di seguito prima di eseguire qualsiasi operazione esso deve attraversare un semaforo di mutua esclusione, nel caso che ciò non possa avvenire il thread rimane in attesa che la risorsa venga liberata e solo a tal punto può continuare la propria esecuzione. protected void getRequest() { while(true) { try { … threadMutex.WaitOne(); mutexIsFree = false; … if(serviceIsOnLine) { if(scadaRemoting.getRemoteObject()) { requestList = scadaRemoting.getAllRequest; scadaRemoting.clearRequest(); scadaRemoting.releaseRemoteObject(); insertRequest(requestList); } } 14 } catch(ThreadInterruptedException t) { getRequestThread.Abort(); MessageBox.Show(t.Message); } finally { threadMutex.ReleaseMutex(); mutexIsFree = true; } } } Per prima cosa viene controllato che il servizio sia attivo, quindi al fine di prevedere un ulteriore livello di sicurezza per l’accesso alla sezione critica viene invocato un metodo implementato sull’oggetto remoto: public bool getRemoteObject() { bool _bRet = false; _bRet = objMutex.WaitOne(10000,true); isFree = false; return _bRet; } In questo l’accesso alla sezione critica risulta controllato da un altro mutex presente all’interno dell’oggetto remoto condiviso. A questo punto ogni singola richiesta pendente viene prelevata dall’Array List e inserita in modo persistente nell’archivio, una volta completata questa procedura si è certi che la richiesta verrà schedulata dal server. La sezione critica viene poi liberata attraverso il seguente metodo: public void releaseRemoteObject() { isFree = true; objMutex.ReleaseMutex(); } 15 Response Engine Fig.5 Al contrario del precedente deve prelevare le richieste, eseguirle e inserire le relative risposte nell’archivio delle risposte. Come il request engine è costruito da un thread che periodicamente deve analizzare l’archivio alla ricerca di nuove richieste: protected void getResponse() { while(true) { try { Thread.Sleep(30000); threadMutex.WaitOne(); mutexIsFree = false; if(serviceIsOnLine) { 16 insertResponse(); } } catch(ThreadInterruptedException t) { getResponseThread.Abort(); MessageBox.Show(t.Message); } finally { threadMutex.ReleaseMutex(); mutexIsFree = true; } } } Attraverso una serie di comandi SQL il metodo insertResponse() verifica la presenza di nuove richieste e in tal caso le estrae, le esegue e ne espone il risultato, la richiesta soddisfatta viene poi eliminata dall’archivio. Ogni richiesta viene gestita mediante la creazione di un nuovo thread “figlio” che si occupa di comunicare con citect e riportare i risultati del comando e eventuali errori di comunicazione. Il thread “padre” rimane in attesa della terminazione attraverso un comando di join(). Politiche di scheduling delle richieste L’utilizzo di database come archivio dati permette la facile implementazione di un qualsiasi algoritmo in modo molto semplice, la strategia di scheduling prevista è quella dell’algoritmo FIFO cioè di eseguire sempre la richiesta pendente che da più tempo è presente nell’archivio. Gestioni Errori Si può presentare la situazione in cui un client cerca di effettuare una o più delle seguenti operazioni: 1. aggiunta di una richiesta nel caso in cui Citect sia offline. In questo caso il server tenterà di eseguire la richiesta e risponderà con un messaggio indicante l’impossibilità di eseguire tale richiesta. 2. lettura/scrittura di un Tag inesistente. Nel caso sopra indicato il server sarà in grado di eseguire la richiesta ma risponderà con un messaggio indicante il fallimento 3. richiesta di esecuzione di una funzione CiCode inesistente Come nel caso precedente 17 Conclusioni I costrutti messi a disposizione dall’architettura .NET hanno notevolmente semplificato lo sviluppo del sistema. Le problematiche incontrate durante l’implementazione sono state risolte in modo più o meno ottimale attraverso la notevole documentazione disponibile in rete riguardo temi di questo tipo. Alla luce delle prove effettuate fino ad ora il sistema presenta buone caratteristiche di stabilità anche se una stima più esatta potrà essere effettuata solo dopo la distribuzione del sistema a più utenti. L’applicazione è stata concepita fin dall’inizio in modo da sperimentare le varie tecnologie attualmente a disposizione per sistemi di questo tipo, è ovvio che il sistema potrebbe essere ulteriormente esteso andando ad incontrare anche tecnologie diverse. Sviluppi futuri: Migliorare la sicurezza utilizzando protocolli di cifratura dei messaggi Replicazione di entità Server per una migliore qualità di servizio Bibliografia Programmare Visual C#.NET Mickey Williams Mondatori Informatica Visual C++ 6 J. Bates T. Tompkins McGrawHill Lucidi del Corso di Calcolatori LS Prof. Antonio Corradi MSDN.Microsoft.com www.codeprojects.com www.mastercsharp.com www.codeguru.com 18