In der klassischen grossen 3-Tier oder n-Tier Businessanwendung versucht man soviel wie möglich von den spezifischen Geschäftsprozessen in eine Business Layer Komponente zu kapseln. Man erreicht dadurch natürlich verschiedene Punkte, die zwei wichtigsten seihen hier kurz erwähnt: Als erstes mal wird natürlich ein Grossteil des Geschäftsprozesses gekappselt und wiederverwendbar gemacht und ausserdem wird natürlich auch die vorhandene Firmeninfrastruktur verborgen. (oder sollte sie zumindest sein) Das .net Framework bietet eine eigene Klassensammlung für Remoting. Für die jenigen unter Ihnen die von Remoting noch nicht viel gehört haben eine kurze Übersicht und warum man so etwas braucht ( oder brauchen könnte ). Remoting ermöglicht es Anwendungen mit anderen Anwendungen zu kommunizieren (Datenaustausch in Form von Objekten), egal ob die Anwendungen auf dem selben Computer laufen oder die Anwendungen über ein Netzwerk verteilt sind, oder ob die Anwendungen auf verschiedenen Plattformen laufen. Das Microsoft .net Framework enthält, wie bereits erwähnt, ein reichhaltiges Objektmodell für Objekte die in anderen Application Domains oder in verschiedenen Prozessen existieren. Um mit der Programmierung beginnen zu können werden wir erst einmal ein paar ( leider theoretische )Grundlagen legen.
.net Remoting Begriffe
- AppDomains: AppDomains sind ein neuer Begriff in der "programmers world". Als AppDomain kann man so etwas wie einen Windows 32 Prozess sehen, jedoch im Gegensatz dazu ist eine AppDomain nicht an das Windows Plattform spezifische Prozess-Thread Modell gebunden. (Das hat wohl was mit .net Framework ...ux zu tun ;-) )
- Messages: Messages sind Objekte die Informationen über den Aufruf einer Remote Methode oder die Rückkehr einer Methode speichern.
- (Serialization) Formatters: Die Serialization Formatters sind ein ganz elementarer Bestandteil des Remoting Konzeptes. Diese Objekte helfen uns überhaupt einen Austausch mit anderen Systemen und deren Objekte durchführen zu können. Sie sind verantwortlich für das Ver- bzw. Entschlüsseln der Messages zwischen den verschiedenen Anwendungen und deren AppDomains. Merken Sie sich einfach: Die Serializiation Formatters bestimmen WIE gesendet wird. Es gibt standardmässig im .net Remoting Framework zwei Formatters, nämlich den Binary Formatter und den SOAP Formatter.
- Channels: Die Channels sind ebenfalls ein wichtiger Bestandteil. Über diese Objekte werden die Messages transportiert ( auch über Firewalls) um die Remote - Kommunikation durchzuführen. Die Channels sind auf dem Client sowie auf dem Server vorhanden. Sie merken sich: WOHIN wird gesendet?
- ObjRef: Das ObjRef Objekt speichert alle relevanten Informationen die benötigt werden um einen Proxy zu erzeugen welcher es ermöglicht mit einem Remote Objekt zu kommunizieren (mit "messages" die durch einen "formatter" behandelt werden und über "channels" verschickt werden). Das ObjRef Objekt wird also benutzt um eine Objekt Referenz über AppDomain Grenzen hinweg zu transferieren. Das Erzeugen einer ObjRef wird auch als Marshaling bezeichnet. Das ObjRef Objekt hält Informationen welche den Typ der Klasse des Objektes welches "gemarshaled" wird, beschreiben, sowie wo dieses Objekt zu finden ist und weitere Informationen die für die Kommunikation wichtig sind, wie die Kommunikation von statten gehen soll.
- Marshaling: Als Marshaling bezeichnet man den Vorgang wenn eine ObjRef aus einem Proxy erzeugt wird.
- UnMarshaling: Als UnMarshaling bezeichnet man den Vorgang, wenn ein Proxy aus einem ObjRef erzeugt wird
.net Remoting Objekte
- Single Call: Single Call Objekte bedienen immer nur einen einzigen Request eines Clients. Single Call Objekte sind für Szenarios sinnvoll, wo diese Objekte bestimmte begrenzte Arbeiten verrichten müssen. Single Call Objekte werden nicht benutzt um "State" Informationen zu speichern und können solche "State" Informationen nicht zwischen Methoden Aufrufen zwischenspeichern. Single Call Objekte ausserdem noch für "load balancing" konfiguriert werden.
- Singelton Objects: Singelton Objekte sind solche Objekte welche mehrere Clients bedienen können und folglich können solche Objekte "State Informationen" und Daten zwischen Client Anforderungen "sharen". Solche Objekte sind sinnvoll in Szenarios wo Daten explizit zwischen Clients "geshared" werden müssen und wo der das erzeugen solcher Objekte mit einem erheblichen Overhead verbunden ist.
- Client-Activated Objects: Client Activated Objects sind server seitige Objekte welche bei einem Request eines Clients aktiviert werden. Diese Art der Aktivierung ist sehr ähnlich der klassischen COM "coclass activation". Wenn der Client einen Request auf ein solches Objekt durch den "new" Operator an den Server gibt, wird eine Aktivierungs- Request Nachricht an die Remote Anwendung geschickt. Der Server erzeugt dann eine Instanz der angeforderten Klasse und gibt ein ObjRef an den Client zurück. Ein Proxy wird dann auf der Client Seite erzeugt, welcher das ObjRef verwendet. Der Aufruf der Methoden wird durch den Proxy selbst abgearbeitet. Client-seitige Objekte können "State" Informationen für diesen einen speziellen Client speichern, jedoch nicht für verschiedene Clients. Jede client seitige Aktivierung mit dem "new" Operator gibt einen Proxy mit einer unabhängigen Instanz des Server zurück.
So, nun haben wir einen Grossteil des benötigten Wissens angelegt. Wollen wir uns nun zum praktischen Teil wenden. Wie Sie bereits oben lesen konnten, gibt es zwei Serzialization Formatters im .net Remoting Framework. Aus diesem Grund werde ich in dem heutigen Teil den ersten Formatter (Binary) und die dazugehörigen Remoting Beispiele durchgehen. Generell gibt es mehrere Möglichkeiten wie Sie .net Remoting Objekte übergeben können, also völlig egal ob sie den Binary oder SOAP Serializiation Formatter benutzen.
1. Es gibt die eine Möglichkeit die Remoting Objekte als Parameter in Methoden Aufrufen zu übergeben.
Sample:
public int myRemoteMethod(MyRemoteObject myObj) |
2. Als Rückgabewert von Methoden Aufrufen
Sample:
public MyRemoteObject myRemoteMethod(string myString) |
3. Werte die aus einer Eigenschaften Sammlung oder einer Klasse kommen
Sample:
Weiterhin haben Sie die Möglichkeit Objekte ByValue zu Marshalen (MBV) oder ByReference zu Marshalen (MBR).
Bei Objekte die per MBV remotet werden, wird eine komplette Kopie des Ojektes gemacht, wenn es von einer Anwendung zu anderen übergeben wird. Bei Objekten die per MBR remotet werden wird eine Referenz auf das Objekt übergeben. Kommt die ObjRef an der Remote Anwendung an, wird es als Proxy Objekt zurückgegeben.
Die Namespaces welche in den Beispielen verwendet wird:
- System.Runtime.Remoting
- System.Runtime.Remoting.Channels
Marshaling Sample:
Hier ein Beispiel für die Rückgabe der Remote Message im SOAP Format:
using System; using System.Runtime.Remoting; using System.IO; using System.Runtime.Serialization.Formatters.Soap; namespace MarshalingTest{ class X : MarshalByRefObject { public void myFunction() { Console.WriteLine("Hallo!"); } } //Hier die eigentliche Anwendung die die Klasse X marshaled class App { static void Main() { MarshalByRefObject x = new X(); ObjRef objRef = RemotingServices.Marshal(x); SoapFormatter sf = new SoapFormatter(); MemoryStream ms = new MemoryStream(); sf.Serialize(ms, objRef); ms.Position=0; StreamReader sr = new StreamReader(ms); Console.WriteLine(sr.ReadToEnd()); } } } |
Unser Binary Formatter Beispiel:
Die oben angegebenen Namespaces werden hierbei um einen weiteren erweitert: System.Runtime.Remoting.Tcp. Dabei wird automatisch der Binary Formatter benutzt. Dieser Formatter serialisiert die Daten in Binäre Form und benutzt "raw sockets" um die Daten über das Netzwerk zu transportieren. Diese Methode sollten Sie verwenden, wenn sie Objekte verteilen wollen ohne über eine Firewall gehen zu müssen.
Als erstes bauen wir uns die eigentliche Funktionalität des Remoting Objekts:
using System;using System.Runtime.Remoting;using System.Runtime.Remoting.Channels;using System.Runtime.Remoting.Channels.Tcp;namespace ppedv_Remoting{ /// <summary> /// Summary description for Class1. /// </summary> public class HelloServer : MarshalByRefObject { public HelloServer() { // // TODO: Add constructor logic here // } public string AktivServer() { return "Hello Server wurde aktiviert!"; } public string SagHallo(string strName) { return "Hallo, " + strName; } } }
|
Ich erzeuge eine Klasse HelloServer welche von MarshalByRefObject abgeleitet wird. Dadurch wird es uns Möglich Objekte per Referenz zu übergeben.
Dann füge ich der Klasse zwei Methoden hinzu wobei die wichtige für uns die SagHallo Methode ist.
Der ganze QuellCode wird in eine .net Komponente kompiliert. Nun zum zweiten Teil des Remoting Objekts. Irgendwie müssen wir dieses Objekt auf der Serverseite dazubringen aufrufbar zu sein. Zu diesem Zweck baue ich nun eine Konsolenanwendung die das obige Objekt auf einen bestimmten Port legt:
using System;using System.IO;using System.Runtime.Remoting;using System.Runtime.Remoting.Channels;using System.Runtime.Remoting.Channels.Tcp;using Remoting_Server_1;namespace ppedv_Remoting{ /// <summary> /// Summary description for cServer. /// </summary> public class cServer { public static void Main() { // // TODO: Add constructor logic here // Console.WriteLine("Der TCP Listener wird auf Port 8089 eingerichtet."); TcpChannel myChannel = new TcpChannel(8089); Console.WriteLine("Listening..."); string keyState = ""; try { ChannelServices.RegisterChannel(myChannel); RemotingConfiguration.RegisterWellKnownServiceType(Type.GetType("ppedv_Remoting.HelloServer,Remoting_Server_1"), "SagHallo", WellKnownObjectMode.SingleCall); while (String.Compare(keyState,"0", true) != 0) { Console.WriteLine("***** Drücken Sie 0 um den Dienst zu beenden *****"); keyState = Console.ReadLine(); } } catch( Exception eXP) { ChannelServices.UnregisterChannel(myChannel); Console.WriteLine(eXP.Message.ToString()); } } public cServer() { Console.WriteLine("Der Dienst wurde aktiviert..."); } ~cServer() { Console.WriteLine("Der Server hat das Remoting Objekt zerstört!"); } } }
|
Die Klasse cServer registiert einen neuen TCP Channel auf Port 8089.
TcpChannel myChannel = new TcpChannel(8089);
...
ChannelServices.RegisterChannel(myChannel);
Danach muss noch die Komponente eingerichtet werden, dass Ihre Funktionalität remote erreichbar ist. Dazu benutze ich die Methode RegisterWellKnownServiceType() der RemotingConfiguration Klasse. Dieser Methode übergebe ich den Namespace und Namen der Klasse welche ich zur Verfügung stellen will und den vollständigen AssemblyNamen. Was dann kommt ist Standard. Ich lasse die Anwendung solange laufen bis der User ein "0" eingibt. Das das natürlich nicht die feine "Englische" ist mir klar, aber wer will kann das ganz ja in einen Windows Service laufen lassen. Das ist sowieso die bessere Implementierung.
Nun zum Client:
Ich erzeuge eine WinForm Anwendung und Sie sehen an dieser Stelle den Quellcode für WinForm1.cs. Wie Sie bereits einleitend feststellen konnten sind die Channels für Remoting auf dem Server und auf dem Client notwendig. Ausserdem haben wir in .net immer noch das Problem, das wir, wie in WinDNA, das Server Objekt (bzw. früher die *.tlb) zu Verfügung haben müssen, da sich sonst der Client nicht kompilieren lässt, weil ihm in unserem Fall Der Typ HelloServer nicht bekannt ist.
using System;using System.Drawing;using System.Collections;using System.ComponentModel;using System.Windows.Forms;using System.Data;using System.Runtime.Remoting;using System.Runtime.Remoting.Channels;using System.Runtime.Remoting.Channels.Tcp;using ppedv_Remoting;//namespace ppedv_Remoting namespace Remoting_Client_Test{ /// <summary> /// Summary description for Form1. /// </summary> public class Form1 : System.Windows.Forms.Form { private System.Windows.Forms.Button cmdCallRemote; private System.Windows.Forms.Label lblRemoteErg; /// <summary> /// Required designer variable. /// </summary> private System.ComponentModel.Container components = null; public Form1() { // // Required for Windows Form Designer support // InitializeComponent(); // // TODO: Add any constructor code after InitializeComponent call // } //Es geht natürlich ein bischen Quellcode ab!!! private void cmdCallRemote_Click(object sender, System.EventArgs e) { //RemotingConfiguration.Configure("RemotingClientTest.exe.config"); TcpChannel myClientChannel = new TcpChannel(); ChannelServices.RegisterChannel(myClientChannel); HelloServer myRemoteObject = (HelloServer)Activator.GetObject(typeof(ppedv_Remoting.HelloServer),"tcp://w2k_dotNET:8089/SagHallo"); if (myRemoteObject == null) { lblRemoteErg.Text = "Could not locate server"; } else { lblRemoteErg.Text = myRemoteObject.SagHallo("Tobi").ToString(); } } } }
|
Ich Registriere erstmal auf dem Client einen TCP Channel, sage aber nicht über welchen Port, den den Weiss der Server. ...
TcpChannel myClientChannel = new TcpChannel();
ChannelServices.RegisterChannel(myClientChannel);
...
Als dann baue ich Instanz des Remote Objekts HelloServer, wobei ich bei Aufruf der Methode Activator.GetObject() die Adresse mitgeben muss, wo sich das Remote Objekt den befindet.
...
HelloServer myRemoteObject = (HelloServer)Activator.GetObject(typeof(ppedv_Remoting.HelloServer),"tcp://w2k_dotNET:8089/SagHallo");
...
Das war?s. Ich brauche jetzt nur noch die Remote Methoden aufrufen, die Anwendung kompilieren und fertig! Mehr im Zweiten Teil und dann sehen Sie ein Beispiel über SOAP Formatters!