Empfänger und Empfängerketten
Channels senden jede Nachricht vor dem Senden oder nach dem Empfang durch eine Kette von Channelempfängerobjekten. Diese Empfängerkette enthält Empfänger, die für die grundlegenden Channelfunktionen erforderlich sind, z. B. Formatierungsempfänger, Transportempfänger oder Empfänger, die Stacks erstellen. Sie können die Channelempfängerkette aber auch für die Ausführung spezieller Aufgaben im Zusammenhang mit einer Nachricht oder einem Stream anpassen.
Jeder Channelempfänger implementiert entweder IClientChannelSink oder IServerChannelSink. Der erste Channelempfänger auf der Clientseite muss außerdem IMessageSink implementieren. Er implementiert in der Regel einen IClientFormatterSink (der von IMessageSink, IChannelSinkBase und IClientChannelSink erbt) und wird als Formatierungsempfänger bezeichnet, da er die eingehende Meldung in einen Stream (ein IMessage-Objekt) transformiert.
Die Channelempfängerkette verarbeitet jede Nachricht, die an eine oder von einer Anwendungsdomäne gesendet wird. Zu diesem Zeitpunkt liegt lediglich die Nachricht als solche vor, Sie können diese aber wie gewünscht verarbeiten. Bei jeder weiteren Verarbeitung wird dann die Nachricht verwendet, die Sie verarbeitet und an das System zurückgegeben haben. Dies ist der ideale Punkt, um einen Protokollierdienst, Filter jeder Art oder auch Verschlüsselung oder andere Sicherheitsmaßnahmen auf dem Client oder Server zu implementieren. In der folgenden Abbildung ist die Struktur einer einfachen Channelempfängerkette zu sehen.
Einfache Channelempfängerkette
Beachten Sie, dass jeder Channelempfänger den Stream verarbeitet und ihn dann an den nächsten Channelempfänger übergibt. Das bedeutet, dass Objekte, die in der Kette vor oder nach dem Empfänger stehen, wissen müssen, wie sie mit dem an sie übergebenen Stream verfahren sollen.
Hinweis Nachrichtenempfänger dürfen keine Ausnahmen auslösen. Eine Möglichkeit, wie Nachrichtenempfänger dies steuern können, besteht darin, Methodencode in try/catch-Blöcke einzubinden.
Channelempfängerprovider (Objekte, die die IClientChannelSinkProvider-Schnittstelle, die IClientFormatterSinkProvider-Schnittstelle oder die IServerChannelSinkProvider-Schnittstelle implementieren) sind für die Erstellung der Channelempfänger zuständig, die Remotenachrichten durchlaufen. Wenn ein Remotetyp aktiviert wird, wird der Channelempfängerprovider aus dem Channel abgerufen. Um den ersten Channel im Empfänger aus der Kette abzurufen, wird die CreateSink-Methode für den Empfängerprovider aufgerufen.
Channelempfänger sind für die Nachrichtenübermittlung zwischen Client und Server zuständig. Sie sind außerdem in einer Kette miteinander verknüpft. Wenn die CreateSink-Methode für einen Empfängerprovider aufgerufen wird, muss dieser Folgendes ausführen:
- Erstellen eines eigenen Channelempfängers;
- Aufrufen von CreateSink für den nächsten Empfängerprovider in der Kette;
- Sicherstellen, dass der nächste und der aktuelle Empfänger miteinander verknüpft sind;
- Zurückgeben des Empfängers an den Aufrufer.
Channelempfänger haben die Aufgabe, alle Aufrufe, die für sie ausgegeben werden, an den nächsten Empfänger in der Kette weiterzuleiten und sollten gewährleisten, dass ein Verfahren für das Speichern eines Verweises auf den nächsten Empfänger bereitgestellt wird.
Channelempfänger sind sehr flexibel bei der Festlegung dessen, was in der Empfängerkette weitergeleitet wird. So können z. B. Sicherheitsempfänger, die vor dem Senden der serialisierten ursprünglichen Nachricht die Authentifizierung aushandeln möchten, die vollständige Channelnachricht zurückhalten, den Inhaltsstream durch eigenen Inhalt ersetzen und ihn dann durch die Empfängerkette zur Remoteanwendungsdomäne senden. Bei der Rückübertragung kann der Sicherheitsempfänger die Antwortnachricht abfangen und einen Dialog mit den entsprechenden Sicherheitsempfängern in der Remoteanwendungsdomäne beginnen. Sobald eine Einigung erzielt wurde, kann der ursprüngliche Sicherheitsempfänger den ursprünglichen Inhaltsstream an die Remoteanwendungsdomäne senden.
Nachrichtenverarbeitung in der Channelempfängerkette
Sobald das .NET Remoting-System einen Channel findet, der die IMethodCallMessage-Implementierung verarbeiten kann, übergibt dieser Channel die Nachricht an den Formatierungschannelempfänger, indem er IMessageSink.SyncProcessMessage (bzw. IMessageSink.AsyncProcessMessage) aufruft. Der Formatierungsempfänger erstellt das Transportheaderarray und ruft IClientChannelSink.GetRequestStream für den nächsten Empfänger auf. Dieser Aufruf wird durch die Empfängerkette weitergeleitet, und jeder Empfänger kann einen Anforderungsstream erstellen, der an den Formatierungsempfänger zurückgegeben wird. Wenn GetRequestStream einen NULL-Verweis (Nothing in Visual Basic) zurückgibt, erstellt der Formatierungsempfänger einen eigenen Empfänger für die Serialisierung. Sobald dieser Aufruf beendet ist, wird die Meldung serialisiert und die entsprechende Meldungsverarbeitungsmethode für den ersten Channelempfänger in der Empfängerkette aufgerufen.
Empfänger können keine Daten in den Stream schreiben, aber Daten aus dem Stream lesen oder bei Bedarf einen neuen Stream weitergeben. Außerdem können Empfänger dem Headerarray Header hinzufügen (wenn sie nicht zuvor GetRequestStream für den nächsten Empfänger aufgerufen haben) und sich selbst vor dem Weiterleiten des Aufrufs an den nächsten Empfänger dem Empfängerstapel hinzufügen. Wenn der Aufruf den Transportempfänger am Ende der Kette erreicht, sendet der Transportempfänger die Header und die serialisierte Nachricht über den Channel an den Server, wo der gesamte Prozess umgekehrt abläuft. Der Transportempfänger (auf der Serverseite) ruft die Header und die serialisierte Nachricht von der Serverseite des Streams ab und leitet diese durch die Empfängerkette bis zum Formatierungsempfänger weiter. Der Formatierungsempfänger deserialisiert die Nachricht und leitet sie an das Remotingsystem weiter, wo sie wieder in einen Methodenaufruf umgewandelt und für das Serverobjekt aufgerufen wird.
Erstellen von Channelempfängerketten
Zum Erstellen eines neuen Channelempfängers müssen Sie das Remotingsystem so implementieren und konfigurieren, dass es eine IServerChannelSinkProvider-Implementierung oder eine IClientChannelSinkProvider-Implementierung erkennt, die die benutzerdefinierte IClientChannelSink-Implementierung oder IServerChannelSink-Implementierung erstellen oder den nächsten Empfänger in der Kette abrufen kann. Sie können die abstrakte BaseChannelSinkWithProperties-Klasse zur Unterstützung bei der Implementierung der benutzerdefinierten Channelempfänger verwenden.
Erstellen eines Channelempfängerproviders
Anwendungen können beim Erstellen eines Channels Channelempfängerprovider für den Client oder Server als Parameter bereitstellen. Channelempfängerprovider sollten in einer Kette gespeichert werden, und es ist die Aufgabe des Benutzers, alle Channelempfängerprovider zu verketten, bevor der äußere an den Channelkonstruktor übergeben wird. Der Channelempfängerprovider implementiert zu diesem Zweck eine Next-Eigenschaft. Im folgenden Codebeispiel wird die Erstellung eines Channelempfängerproviders auf der Clientseite veranschaulicht. Ein vollständiges Beispiel finden Sie unter Remotingbeispiel: Channelempfängerprovider.
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;
}
Hinweis Wenn in einer Konfigurationsdatei mehrere Channelempfängerprovider angegeben sind, werden diese vom Remotingsystem in der Reihenfolge verkettet, in der sie in der Konfigurationsdatei gefunden werden. Die Channelempfängerprovider werden erstellt, wenn der Channel während des Aufrufs von RemotingConfiguration.Configure erstellt wird.
Formatierungsempfänger
Formatierungsempfänger serialisieren die Channelmeldung als Objekt, das IMessage implementiert, in den Meldungsstream. Einige Implementierungen von Formatierungsempfängern verwenden die vom System bereitgestellten Typen von Formatierungsprogrammen (BinaryFormatter und SoapFormatter). Andere Implementierungen verfügen über eigene Möglichkeiten, um die Channelnachricht in den Stream zu transformieren.
Zweck des Formatierungsempfängers ist es, die erforderlichen Header zu erstellen und die Nachricht in den Stream zu serialisieren. Wenn der Formatierungsempfänger erreicht ist, wird die Meldung durch den Aufruf von IMessageSink.ProcessMessage oder Imessagesink.AsyncProcessMessage an alle Empfänger in der Empfängerkette weitergeleitet. Zu diesem Zeitpunkt ist die Nachricht bereits serialisiert und wird lediglich zu Informationszwecken bereitgestellt.
Hinweis Empfänger, die die Nachricht erstellen oder anpassen müssen, müssen in der Empfängerkette vor dem Formatierungsprogramm platziert werden. Dies lässt sich problemlos durch die Implementierung von IClientFormatterSink bewerkstelligen, wodurch dem System glaubhaft gemacht wird, es verfüge über einen Verweis auf den Formatierungsempfänger. Der echte Formatierungsempfänger kann dann später in der Empfängerkette platziert werden.
Bei der Rückübertragung transformiert der Formatierungsempfänger den Nachrichtenstream wieder in die Elemente der Channelnachricht (Rückgabemeldung). Der erste Empfänger auf der Clientseite muss die IClientFormatterSink-Schnittstelle implementieren. Wenn CreateSink an den Channel zurückgegeben wird, wird der zurückgegebene Verweis in einen IClientFormatterSink-Typ umgewandelt, so dass die SyncProcessMessage der IMessage-Schnittstelle aufgerufen werden kann. Falls die Umwandlung fehlschlägt, löst das System eine Ausnahme aus.
Benutzerdefinierte Channelempfänger
Auf der Clientseite werden benutzerdefinierte Channelempfänger in die Kette von Objekten zwischen dem Formatierungsempfänger und dem letzten Transportempfänger eingefügt. Durch das Einfügen eines benutzerdefinierten Channelempfängers in den Client- oder Serverchannel sind Sie in der Lage, die IMessage an einem von zwei Punkten zu verarbeiten:
- während des Prozesses, durch den ein als Nachricht dargestellter Aufruf in einen Stream konvertiert und über die Verbindung gesendet wird;
- während des Prozesses, durch den ein Stream aus der Übertragung gelesen und an das StackBuilderSink-Objekt (den letzten Nachrichtenempfänger vor dem Remoteobjekt auf dem Server) bzw. das Proxyobjekt (auf dem Client) gesendet wird.
Benutzerdefinierte Empfänger können Daten aus dem Stream lesen oder in den Stream schreiben (je nachdem, ob es sich um einen ein- oder ausgehenden Aufruf handelt) und ggf. den Headern zusätzliche Informationen hinzufügen. Zu diesem Zeitpunkt ist die Nachricht bereits durch das Formatierungsprogramm serialisiert worden und kann nicht geändert werden. Wenn der Nachrichtenaufruf an den Transportempfänger am Ende der Kette weitergeleitet wird, schreibt der Transportempfänger die Header in den Stream und leitet den Stream unter Verwendung des von dem Channel vorgegebenen Übertragungsprotokolls an den Transportempfänger auf dem Server weiter.
Transportempfänger
Der Transportempfänger ist der letzte Empfänger in der Kette auf der Clientseite und der erste Empfänger in der Kette auf der Serverseite. Neben der Übermittlung der serialisierten Nachricht hat der Transportempfänger auch die Aufgabe, die Header an den Server zu senden und die Header sowie den Stream bei Rückgabe des Aufrufs vom Server abzurufen. Diese Empfänger sind in den Channel eingebaut und können nicht erweitert werden.
Ersetzen des Standardformatierungsprogramms
Da es sich bei einem Channel um einen abstrakten Netzwerkmechanismus handelt, können Sie das .NET Remoting-System so konfigurieren, dass ein vom System implementierter Channel mit einem beliebigen Formatierungsprogramm kombiniert wird. Sie können zu diesem Zweck den Channelkonstruktor verwenden, der eine IDictionary-Implementierung mit Channeleigenschaften, ein serverseitiges Formatierungsprogramm und ein clientseitiges Formatierungsprogramm akzeptiert. Sie können das Formatierungsprogramm auch in einer Konfigurationsdatei angeben. Im folgenden Beispiel wird das .NET Remoting-Konfigurationssystem angewiesen, einen HttpChannel zu erstellen, jedoch auf der Clientseite BinaryClientFormatterSink zu verwenden.
<configuration>
<system.runtime.remoting>
<application>
<channels>
<channel ref="http">
<clientProviders>
<formatter ref="binary"/>
</clientProviders>
<channels>
</application>
</system.runtime.remoting>
</configuration>
Im folgenden Code wird dieselbe Aufgabe programmgesteuert ausgeführt, wobei der Remoteschnittstellentyp IService angenommen wird, der GetServerString und GetServerTime implementiert.
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());
}
}
Ein vollständiges Beispiel für diese in IIS (Internet-Informationsdienste) gehostete Kombination aus Channel und Formatierungsprogramm finden Sie unter Remotingbeispiel: Hosting in Internet Information Services (IIS).
Wenn dieser Client so geändert werden soll, dass ein TcpChannel-Objekt mit dem SoapClientFormatterSink-Objekt verwendet wird, müssen Sie nur die Namespaces und den Aufruf von RegisterChannel ändern, wie im folgenden Codebeispiel veranschaulicht.
ChannelServices.RegisterChannel(New TcpChannel(properties, NewSoapClientFormatterSinkProvider(), Nothing))
[C#]
ChannelServices.RegisterChannel(new TcpChannel(null, new SoapClientFormatterSinkProvider(), null));
Siehe auch
Erweitertes Remoting | Hosting von Remoteobjekten in Internet Information Services (IIS) | Remotingbeispiel: Hosting in Internet Information Services (IIS)