Sink e catene dei sink

Ogni messaggio viene inviato dai canali lungo una catena di oggetti sink di canale prima di inviare un messaggio o dopo averlo ricevuto. Questa catena di sink contiene i sink necessari per le funzionalità di base del canale, come i sink del formattatore, di trasporto o del generatore di stack, ma è possibile personalizzare la catena dei sink di canale per eseguire attività speciali con un messaggio o un flusso.

Ogni sink di canale implementa IClientChannelSink o IServerChannelSink. È anche necessario che il primo sink di canale sul lato client implementi IMessageSink. In genere viene implementata IClientFormatterSink, che eredita da ImessageSink, IChannelSinkBase e IClientChannelSink ed è denominata sink del formattatore in quanto consente di trasformare il messaggio in arrivo in un flusso, ossia in un oggetto IMessage.

Qualsiasi messaggio inviato a o da un dominio applicazione viene elaborato dalla catena di sink di canale. Con il messaggio ottenuto è possibile eseguire varie operazioni e nelle successive elaborazioni verrà utilizzato il messaggio restituito al sistema dopo l'elaborazione. È possibile implementare in questa posizione un servizio di registrazione, qualsiasi tipo di filtro, sistemi di crittografia o altre misure di protezione su client o server. Nell'illustrazione riportata di seguito viene mostrata la struttura di una catena dei sink di canale di base.

Catena dei sink di canale di base

Ciascun sink di canale consente di elaborare il flusso e di passarlo al sink di canale successivo, in modo da indicare agli oggetti precedenti o successivi al sink quali operazioni eseguire con il flusso passato.

Nota   I sink dei messaggi non devono generare eccezioni. A tal fine, inserire il codice di metodo in blocchi try-catch.

I provider di sink di canale, vale a dire gli oggetti che implementano l'interfaccia IClientChannelSinkProvider, IClientFormatterSinkProvider o IServerChannelSinkProvider, sono responsabili della creazione dei sink di canale attraverso i quali scorrono i messaggi di comunicazione remota. Quando viene attivato un tipo remoto, il provider di sink di canale viene recuperato dal canale e il metodo CreateSink viene chiamato su tale provider di sink per recuperare dalla catena il primo canale nel sink.

I sink di canale sono responsabili del trasporto di messaggi tra client e server e sono collegati in una catena. Quando il metodo CreateSink viene chiamato su un provider di sink, deve eseguire le operazioni riportate di seguito.

  • Creare il proprio sink di canale.
  • Chiamare CreateSink sul provider di sink successivo nella catena.
  • Assicurarsi che il sink successivo e quello corrente siano collegati.
  • Restituire il sink al chiamante.

I sink di canale hanno la funzione di inoltrare al sink successivo nella catena tutte le chiamate effettuate su di essi oltre a fornire un meccanismo per la memorizzazione di un riferimento al sink successivo.

I sink di canale sono caratterizzati da una grande flessibilità relativamente agli oggetti inviati sulla catena di sink. I sink di protezione che negoziano l'autenticazione prima di inviare l'effettivo messaggio originale serializzato, ad esempio, possono mantenere il messaggio di canale completo, sostituire il flusso di contenuto con il proprio contenuto e inviarlo sulla catena di sink e verso il dominio applicazione remoto. Nel viaggio di ritorno il messaggio di risposta può essere intercettato dal sink di protezione, dando origine a una conversazione con i sink di protezione corrispondenti nel dominio applicazione remoto. Una volta raggiunto un accordo, il sink di protezione di origine può inviare il flusso di contenuto originale al dominio applicazione remoto.

Elaborazione di messaggi nella catena di sink di canale

Dopo l'individuazione da parte del sistema .NET Remoting di un canale in grado di elaborare l'implementazione di IMethodCallMessage, il messaggio viene passato dal canale al sink di canale del formattatore mediante la chiamata a IMessageSink.SyncProcessMessage o a IMessageSink.AsyncProcessMessage. Il sink del formattatore crea la matrice di intestazione del trasporto e chiama IClientChannelSink.GetRequestStream sul sink successivo. La chiamata viene inoltrata lungo la catena del sink e qualunque sink può creare un flusso di richiesta che verrà passato al sink del formattatore. Se GetRequestStream restituisce un riferimento null (Nothing in Visual Basic), dal sink del formattatore verrà creato un sink per la serializzazione. Una volta restituita la chiamata, il messaggio viene serializzato e il metodo appropriato per l'elaborazione del messaggio è chiamato sul primo sink di canale nella catena.

I sink non possono scrivere dati nel flusso, ma sono in grado di leggere dal flusso o passarne uno nuovo se richiesto. Consentono anche di aggiungere intestazioni alle matrici di intestazione, se in precedenza non è stata effettuata una chiamata a GetRequestStream sul sink successivo, e possono essere aggiunti allo stack di sink prima di inoltrare la chiamata al sink successivo. Quando la chiamata raggiunge il sink di trasporto alla fine della catena, questo invia le intestazioni e il messaggio serializzato sul canale al server dove viene invertito l'intero processo. Il sink di trasporto sul lato server recupera le intestazioni e il messaggio serializzato dal lato server del flusso e li inoltra sulla catena di sink fino a raggiungere il sink del formattatore. Il messaggio viene deserializzato dal sink del formattatore e inoltrato al sistema remoto dove diventa una chiamata di metodo e viene richiamato sull'oggetto server.

Creazione di catene dei sink di canale

Per creare un nuovo sink di canale, è necessario implementare e configurare il sistema di comunicazione remota per riconoscere un'implementazione di IServerChannelSinkProvider o IClientChannelSinkProvider, con cui è possibile creare l'implementazione personalizzata IClientChannelSink o IServerChannelSink oppure recuperare il sink successivo nella catena. È possibile utilizzare la classe abstract BaseChannelSinkWithProperties per facilitare l'implementazione dei sink di canale personalizzati.

Generazione di un provider di sink di canale

Le applicazioni possono fornire i provider di sink di canale client o server come parametri durante la costruzione di un canale. È necessario memorizzare i provider di sink di canale in una catena e l'utente dovrà concatenare tutti i provider prima di passare quello esterno al costruttore di canale. A tal fine, il provider di sink di canale consente di implementare una proprietà Next. Nell'esempio di codice riportato di seguito viene illustrato come generare un provider di sink di canale sul lato client. Un esempio completo è disponibile nella sezione Esempio di comunicazione remota: provider di sink di canale.

private Function CreateDefaultClientProviderChain() As IClientChannelSinkProvider
   Dim chain As New FirstClientFormatterSinkProvider            
   Dim sink As IClientChannelSinkProvider
   sink = chain
   sink.Next = New SecondClientFormatterSinkProvider
   sink = sink.Next
   return chain
End Function 
[C#]
private IClientChannelSinkProvider CreateDefaultClientProviderChain(){
   IClientChannelSinkProvider chain = new FirstClientFormatterSinkProvider();            
   IClientChannelSinkProvider sink = chain;
   sink.Next = new SecondClientFormatterSinkProvider();
   sink = sink.Next;
   return chain;
} 

Nota   Quando in un file di configurazione sono presenti più provider di sink di canale, il sistema di comunicazione remota li concatena nell'ordine in cui si presentano in tale file. I provider di sink di canale vengono creati con la creazione del canale nel corso della chiamata a RemotingConfiguration.Configure.

Sink del formattatore

Il messaggio del canale viene serializzato nel flusso del messaggio dai sink del formattatore come oggetto che implementa IMessage. In alcune implementazioni di sink del formattatore vengono utilizzati i tipi di formattatori forniti dal sistema, BinaryFormatter e SoapFormatter. Altre implementazioni utilizzano i propri mezzi per trasformare il messaggio del canale nel flusso.

La funzione del sink del formattatore consiste nel generare le intestazioni necessarie e nel serializzare il messaggio nel flusso. Dopo il sink del formattatore, il messaggio viene inoltrato a tutti i sink della catena mediante le chiamate a IMessageSink.ProcessMessage o a Imessagesink.AsyncProcessMessage. In questa fase, il messaggio è già stato serializzato e viene fornito solo come informazione.

Nota   I sink che richiedono la creazione o la modifica del messaggio stesso devono essere collocati prima del formattatore nella catena di sink. Questo risultato si ottiene facilmente con l'implementazione di IClientFormatterSink, mediante cui il sistema ritiene di avere un riferimento al sink del formattatore. Il vero sink del formattatore può essere inserito più avanti nella catena di sink.

Nel viaggio di ritorno, il flusso del messaggio viene trasformato dal sink del formattatore negli elementi del messaggio del canale (messaggio restituito). Il primo sink sul lato client deve implementare l'interfaccia IClientFormatterSink. Quando CreateSink viene restituito al canale, si esegue il cast del riferimento restituito in un tipo IClientFormatterSink in modo da poter chiamare SyncProcessMessage dell'interfaccia IMessage. Se il cast non ha esito positivo, viene generata un'eccezione.

Sink di canale personalizzati

Sul lato client i sink di canale personalizzati vengono inseriti nella catena di oggetti tra il sink del formattatore e l'ultimo sink di trasporto. Con l'inserimento di un sink di canale personalizzato nel canale client o server è possibile elaborare IMessage in una delle due situazioni elencate di seguito.

  • Durante il processo che converte in un flusso e invia sulla connessione una chiamata rappresentata come messaggio.
  • Durante il processo mediante il quale viene eliminato un flusso dalla connessione e viene inviato all'oggetto StackBuilderSink, ossia all'ultimo sink di messaggi prima dell'oggetto remoto sul server, o all'oggetto proxy sul client.

I sink personalizzati possono leggere o scrivere dati nel flusso, a seconda che la chiamata sia in uscita o in ingresso, e aggiungere eventuali altre informazioni alle intestazioni. In questa fase, il messaggio è già stato serializzato dal formattatore e non può essere modificato. Quando la chiamata del messaggio è inoltrata al sink di trasporto alla fine della catena, le intestazioni vengono scritte dal sink di trasporto nel flusso, il quale viene inoltrato al sink di trasporto sul server mediante il protocollo di trasporto imposto dal canale.

Sink di trasporto

Il sink di trasporto è l'ultimo sink della catena sul lato client e il primo della catena sul lato server. Oltre a trasportare il messaggio serializzato, il sink di trasporto invia anche le intestazioni al server e recupera il flusso e le intestazioni quando la chiamata viene restituita dal server. Questi sink vengono generati nel canale e non possono essere estesi.

Sostituzione del formattatore predefinito

Poiché un canale è un meccanismo di rete astratto, è possibile configurare il sistema .NET Remoting per combinare un canale implementato dal sistema con qualsiasi formattatore scelto. A tal fine si può utilizzare il costruttore di canale che accetta un'implementazione di IDictionary di proprietà del canale, un formattatore sul lato server e uno sul lato client. È anche possibile specificare il formattatore in un file di configurazione. Nell'esempio di codice che segue viene illustrato come far sì che mediante il sistema di configurazione di .NET Remoting venga creato un oggetto HttpChannel, pur con l'utilizzo di BinaryClientFormatterSink sul lato client.

<configuration>
   <system.runtime.remoting>
      <application>
         <channels>
            <channel ref="http">
               <clientProviders>
                  <formatter ref="binary"/>
               </clientProviders>
         <channels>
      </application>
   </system.runtime.remoting>
</configuration> 

Nell'esempio di codice che segue le stesse operazioni vengono eseguite a livello di codice, presupponendo un tipo di interfaccia remota IService che consente di implementare GetServerString e GetServerTime.

Imports System
Imports System.Collections
Imports System.Runtime.Remoting
Imports System.Runtime.Remoting.Channels
Imports System.Runtime.Remoting.Channels.Http

Public Class ClientProcess
  <MTAThread()> _
  Public Shared Sub Main()
      
    ' Note that any name/value pairs of configuration attributes can be 
    ' placed in this dictionary (the configuration system calls this same 
    ' constructor).
    Dim properties As New Hashtable()
    properties("name") = "HttpBinary"
     
    ChannelServices.RegisterChannel(New HttpChannel(properties, New BinaryClientFormatterSinkProvider(), Nothing))
    ' The last parameter above (Nothing) is the server sink provider chain 
    ' to obtain the default behavior (which includes SOAP and 
    ' binary formatters on the server side).
    Dim service As IService = CType(Activator.GetObject(GetType(IService), "http://computer:8080/SAService"), IService)
      
    Console.WriteLine("Server string is: " + service.GetServerString())
    Console.WriteLine("Server time is: " + service.GetServerTime())
  End Sub
   
End Class 
[C#]
using System;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;

public class ClientProcess{

  public static void Main(string[] Args){
        
    // Note that any name/value pairs of configuration attributes can be 
    // placed in this dictionary (the configuration system calls this 
    // same HttpChannel constructor).
    IDictionary properties = new Hashtable();
    properties["name"] = "HttpBinary";

    // The last parameter below is the server sink provider chain 
    // to obtain the default behavior (which includes SOAP and binary 
    // formatters) on the server side.
    ChannelServices.RegisterChannel(new HttpChannel(null, new BinaryClientFormatterSinkProvider(), null));

    IService service = (IService)Activator.GetObject(typeof(IService),"http://computer:8080/SAService");
        
    Console.WriteLine("Server string is: " + service.GetServerString());
    Console.WriteLine("Server time is: " + service.GetServerTime());      
  }
}

Per un esempio completo della combinazione qui definita di canale e formattatore contenuta in Internet Information Services (IIS), vedere Esempio di comunicazione remota: hosting in Internet Information Services (IIS).

Per modificare il client indicato in precedenza affinché utilizzi un oggetto TcpChannel con l'oggetto SoapClientFormatterSink, è sufficiente modificare gli spazi dei nomi e la chiamata a RegisterChannel, come illustrato nell'esempio di codice riportato di seguito.

ChannelServices.RegisterChannel(New TcpChannel(properties, NewSoapClientFormatterSinkProvider(), Nothing))
[C#]
ChannelServices.RegisterChannel(new TcpChannel(null, new SoapClientFormatterSinkProvider(), null));

Vedere anche

Comunicazione remota avanzata | Hosting di oggetti remoti in Internet Information Services (IIS) | Esempio di comunicazione remota: hosting in Internet Information Services (IIS)