Remoting | ||
Differenze tra Value e Reference Type |
Introduzione.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. Tutte le codifiche XML utilizzano il protocollo SOAP per la trasmissione dei messaggi da un application domain a un altro. Il remoting è stato progettato pensando alla protezione; sono disponibili una serie di hook che fanno in modo che i sink di protezione abbiano accesso ai messaggi, insieme al flusso serializzato, prima che il flusso venga trasmesso nel canale. La gestione della durata degli oggetti remoti senza un supporto della struttura sottostante non è sempre facile. .NET remoting mette a disposizione una serie di modelli di durata tra cui scegliere, suddivisi in due categorie:
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 remotiUno 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. Quando l'oggetto viene passato in forma di parametro, la struttura serializza l'oggetto e lo trasporta all'application domain di destinazione, dove l'oggetto verrà ricomposto. Gli oggetti locali che non è possibile serializzare non possono essere spostati in un altro application domain quindi vengono considerati non remoti. È 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 metodo viene richiamato. La stessa procedura viene utilizzata per riportare i risultati della chiamata del metodo. Oggetti proxyGli oggetti proxy vengono creati quando un client attiva un oggetto remoto. L'oggetto proxy agisce da rappresentante degli oggetti remoti e controlla che tutte le chiamate fatte sul proxy vengano inoltrate all'istanza corretta dell'oggetto remoto. Per capire esattamente come funzionano gli oggetti proxy è necessario esaminarli più da vicino. Quando un client attiva un oggetto remoto, la struttura crea un'istanza locale della classe TransparentProxy che contiene un elenco di tutte le classi, insieme a metodi di interfaccia dell'oggetto remoto. Dal momento che la classe TransparentProxy una volta creata viene registrata con CLR, tutte le chiamate del metodo sul proxy vengono intercettate dal runtime. La chiamata viene quindi esaminata per stabilire se si tratta di un metodo valido dell'oggetto remoto e se un'istanza di questo oggetto si trova nello stesso application domain del proxy. Se tutto ciò è vero, una chiamata semplice del metodo viene indirizzata all'oggetto reale. Se l'oggetto si trova in un application domain diverso, i parametri di chiamata sullo stack vengono assemblati in un oggetto IMessage e inoltrati a una classe RealProxy richiamando il relativo metodo Invoke. Questa classe (o meglio, un'implementazione interna della classe) si occupa di inoltrare i messaggi all'oggetto remoto. Entrambe le classi TransparentProxy e RealProxy vengono create automaticamente quando viene attivato un oggetto remoto, ma viene restituito al client solo TransparentProxy. Per una migliore comprensione di questi oggetti proxy, dobbiamo parlare brevemente di ObjRef. Nella Sezione Attivazione è contenuta una descrizione dettagliata di ObjRef. Lo scenario seguente descrive in breve il modo in cui ObjRef e le due classi proxy sono correlate. Si tratta di una descrizione molto ampia del processo ed esistono alcune variazioni, a seconda che gli oggetti siano attivati da client o da server e se si tratta di oggetti singleton o single-call.
TransparentProxy è una classe interna che non può essere sostituita nè estesa. Allo stesso tempo, le classi RealProxy e ObjRef sono pubbliche e possono essere estese e personalizzate laddove necessario. La classe RealProxy è un elemento ideale per eseguire ad esempio il bilanciamento del carico, perché gestisce tutte le chiamate di funzioni su un oggetto remoto. Quando viene richiamato Invoke, una classe che deriva da RealProxy può ottenere informazioni di carico sui server della rete e indirizzare la chiamata a un server adatto. È necessario un MessageSink per ObjectURI richiesto dal canale e richiamare SyncProcessMessage o AsyncProcessMessage per inoltrare la chiamata all'oggetto remoto richiesto. Quando la chiamata viene reinviata, il parametro di ritorno viene riportato allo stack, richiamando PropagateMessageToProxy sulla classe RemotingServices. Di seguito è riportato un frammento di codice che mostra come utilizzare una classe derivata RealProxy. MyRealProxy proxy = new MyRealProxy(typeof(Foo)); È possibile inoltrare il valore TransparentProxy ottenuto sopra verso un altro application domain. Quando il secondo client cerca di richiamare un metodo sul proxy, la struttura di remoting prova a creare un'istanza di MyRealProxy e se all'assembly è disponibile, tutte le chiamate verranno indirizzate attraverso questa istanza. Se l'assembly non è disponibile, le chiamate vengono indirizzate attraverso il RealProxy di remoting predefinito. È possibile personalizzare in modo semplice ObjRef fornendo dei valori sostitutivi per le proprietà ObjRef, TypeInfo, EnvoyInfo e ChannelInfo. Il codice seguente indica come eseguire questa operazione. public class ObjRef { public virtual IRemotingTypeInfo TypeInfo { get { return typeInfo;} set { typeInfo = value;} } public virtual IEnvoyInfo EnvoyInfo { get { return envoyInfo;} set { envoyInfo = value;} } public virtual IChannelInfo ChannelInfo { get { return channelInfo;} set { channelInfo = value;} } } CanaliI 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:
Tutti i canali derivano da IChannel e implementano IChannelReceiver o IchannelSender, a seconda dello scopo del canale. La maggior parte di essi implementa le interfacce di ricevente e mittente per consentire loro di comunicare in entrambe le direzioni. Quando un client richiama un metodo su un proxy, la chiamata viene intercettata dalla struttura di remoting e trasformata in un messaggio, che viene inoltrato alla classe RealProxy (o meglio, a un'istanza di una classe che implementa RealProxy). RealProxy inoltra il messaggio al sink per l'elaborazione. Un sink di messaggio si occupa di stabilire la connessione con il canale registrato dall'oggetto remoto e di trasportare il messaggio attraverso il canale (in un altro application domain), da cui viene indirizzato all'oggetto remoto stesso. Quando un oggetto remoto viene attivato, un sink di messaggio in grado di comunicare con l'oggetto remoto viene recuperato dal canale prescelto dal client, richiamando CreateMessageSink su questo canale. Un aspetto poco chiaro della struttura di remoting è il rapporto tra oggetti remoti e canali. Ad esempio, in che modo un oggetto remoto SingleCall attende che i client si connettano, se l'oggetto viene attivato solo quando arriva la chiamata? Una parte di questa operazione dipende dal fatto che gli oggetti remoti condividono i canali. Un oggetto remoto non possiede un canale. Le applicazioni server che contengono gli oggetti remoti devono registrare i canali necessari e gli oggetti da esporre con la struttura di remoting. Quando un canale viene registrato, automaticamente comincia ad attendere le richieste del client alla porta specificata. Quando un oggetto remoto viene registrato, viene creato un ObjRef per l'oggetto, che viene memorizzato in una tabella. Quando arriva una richiesta su un canale, la struttura di remoting esamina il messaggio per determinare l'oggetto di destinazione e controlla la tabella dei riferimenti all'oggetto per individuare il riferimento all'interno della tabella. Se questo riferimento viene individuato, l'oggetto di destinazione della struttura viene recuperato dalla tabella o attivato quando è necessario, e la struttura inoltra la chiamata all'oggetto. Nel caso di chiamate sincrone, la connessione del client resta attiva per la durata della chiamata del messaggio. Dal momento che ogni connessione client viene gestita nel relativo thread, un singolo canale può essere utilizzato da più client contemporaneamente. La protezione è un fattore importante nella creazione di applicazioni aziendali e gli sviluppatori devono essere in grado di aggiungere funzionalità di protezione, per esempio autorizzazione o codifica per le chiamate remote di metodo, per soddisfare i requisiti aziendali. Per far fronte a questa esigenza, è possibile personalizzare i canali per fornire agli sviluppatori il controllo del meccanismo concreto di trasmissione dei messaggi da e verso l'oggetto remoto. Tutti i messaggi passano attraverso SecuritySink, TransportSink e FormatterSink prima di essere trasferiti all'applicazione remota, da cui poi passano attraverso gli stessi sink in ordine inverso. Canale HTTPIl canale HTTP trasporta i messaggi da e verso oggetti remoti utilizzando il protocollo SOAP. Tutti i messaggi passano attraverso il formattatore SOAP, dove il messaggio viene trasformato in XML e serializzato, e le intestazioni SOAP vengono aggiunte al flusso. È possibile inoltre specificare il formattatore binario, che crea un flusso di dati binari. Questo flusso viene quindi trasportato all'URI di destinazione tramite il protocollo HTTP. Canale TCPIl 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. AttivazioneLa 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. È necessario registrare tutti gli oggetti
remoti con la struttura remota prima che i client possano accedervi.
La registrazione degli oggetti viene eseguita in genere da un'applicazione
host che avvia, registra uno o più canali con ChannelServices,
registra uno o più oggetti remoti con RemotingServices
e aspetta che l'operazione sia terminata. I canali registrati e
gli oggetti sono disponibili solo mentre il processo che li ha registrati
è aperto. Quando il processo termina, tutti i canali e gli
oggetti registrati vengono automaticamente rimossi dai servizi di
remoting in cui si trovano. Le informazioni riportate di seguito
sono necessarie per la registrazione di un oggetto remoto con la
struttura:
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 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
È possibile utilizzare GetObject o new per l'attivazione del server. L'oggetto non viene 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. Il leasing di oggetti è descritto nella sezione successiva. Durata oggetti con leasingOgni application domain contiene un programma di gestione lease all'interno del dominio. Tutti i lease vengono esaminati periodicamente per controllare le scadenze. Se un lease è scaduto, è possibile richiamare gli sponsor del lease dando loro l'opportunità di rinnovarlo. Se nessuno degli sponsor decide per il rinnovo, il programma di gestione rimuove il lease e l'oggetto viene sottoposto a garbage collection. Questo programma gestisce un elenco che ordina tutti i lease in base al tempo residuo del contratto. I lease più vicini alla scadenza vengono collocati ai primi posti dell'elenco. I lease implementano l'interfaccia ILease e contengono una serie di proprietà, che determinano i criteri e i metodi da rinnovare. I lease possono essere rinnovati su richiesta. Ogni volta che viene richiamato un metodo sull'oggetto remoto, il tempo di lease viene impostato sul massimo valore corrente di LeaseTime più RenewOnCallTime. Alla scadenza del LeaseTime, viene richiesto allo sponsor di rinnovare il lease. A causa delle reti talvolta poco affidabili, potrebbe accadere che lo sponsor del lease non sia disponibile, quindi per essere sicuri di non lasciare oggetti bloccatisul server, ogni lease ha un SponsorshipTimeout. Questo valore specifica il tempo di attesa per la risposta dello sponsor prima che il lease venga chiuso. Se SponsershipTimeout è nullo, il valore CurrentLeaseTime verrà utilizzato per stabilire la scadenza del lease. Se CurrentLeaseTime ha un valore zero, il lease non scade. È possibile utilizzare la configurazione o le API per sostituire i valori predefiniti di InitialLeaseTime, SponsorshipTimeout e RenewOnCallTime. Il programma di gestione lease crea un elenco degli sponsor (che implementano l'interfaccia ISponsor) con ordine decrescente di sponsorship. Quando occorre uno sponsor per rinnovare i termini del lease, viene interpellato uno o più sponsor dalle prime voci dell'elenco. I primi nomi sono quelli degli sponsor che hanno richiesto in precedenza i tempi di rinnovo di lease più lunghi. Se uno sponsor non risponde nell'intervallo di tempo SponsorshipTimeOut, il suo nome viene eliminato dall'elenco. È possibile ottenere un lease richiamando GetLifetimeService, passando il lease richiesto come parametro. Questa chiamata costituisce un metodo statico della classe RemotingServices. Se l'oggetto è locale al dominio dell'applicazione, il parametro per questa chiamata è un riferimento locale all'oggetto, e il lease restituito è un riferimento locale al lease. Se l'oggetto è remoto, il proxy viene passato come parametro e al mittente viene restituito un proxy trasparente per il lease. Gli oggetti possono fornire i relativi lease e avere così il controllo della durata; ciò è possibile sostituendo il metodo InitializeLifetimeService a MarshalByRefObject come indicato di seguito: public class Foo : MarshalByRefObject { È possibile modificare le proprietà di lease solo quando un lease si trova nello stato iniziale. Normalmente l'implementazione di InitializeLifetimeService richiama il metodo corrispondente della classe base per recuperare il lease esistente per l'oggetto remoto. Se non è mai stato eseguito il marshalling su questo oggetto, il lease restituito è allo stato iniziale, quindi è possibile impostare le proprietà. Una volta eseguito il marshalling sull'oggetto, il lease passa dallo stato iniziale allo stato attivo e qualsiasi tentativo di inizializzare le proprietà di lease verrà ignorato (viene lanciata un'eccezione). All'attivazione dell'oggetto remoto viene richiamato InitializeLifetimeService. All'attivazione della chiamata è possibile avere un elenco degli sponsor del lease e aggiungere altri nomi di sponsor in qualsiasi momento, mentre il lease è attivo. I tempi di lease possono essere estesi in base a quanto riportato di seguito:
Quando un lease scade, il suo stato interno passa da Active a Expired, non vengono eseguite altre chiamate agli sponsor e l'oggetto viene sottoposto a garbage collection. Poiché è spesso difficile richiamare uno sponsor per gli oggetti remoti, se lo sponsor viene pubblicato sul Web o è protetto da un firewall, non deve necessariamente trovarsi nella stessa posizione del client; è sufficiente che sia in un punto della rete raggiungibile dall'oggetto remoto. L'utilizzo dei lease per gestire la durata degli oggetti remoti è un metodo alternativo per il conteggio, che in genere è complesso e inefficace per le connessioni di rete non affidabili. Anche se può sembrare che la durata di un oggetto remoto venga estesa più del necessario, la riduzione del traffico di rete dovuto a questo conteggio e il ping dei client rende il leasing una soluzione molto attraente. Esempio di remoting con un canale TCPQuesta 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 System; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using 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; } } } Questo codice deve essere salvato come client.cs: using System; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using 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.HelloServer) , "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 System; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using 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; } } } Questo è il makefile: all: object.dll server.exe client.exe object.dll: share.cs csc /debug+ /target:library /out:object.dll object.cs server.exe: server.cs csc /debug+ /r:object.dll /r:System.Runtime.Remoting.dll server.cs client.exe: client.cs server.exe csc /debug+ /r:object.dll /r:server.exe /r:System.Runtime.Remoting.dll client.cs E questi i risultati!: Fonti: |