Microsoft .NET und sein Framework

Veröffentlicht: 05. Feb 2002 | Aktualisiert: 14. Jun 2004

Von Tobias Ulm

Remoting in .NET ermöglicht es Anwendungen untereinander zu kommunizieren (Datenaustausch in Form von Objekten), unabhängig von "Ort" und Plattform. Das Microsoft .NET Framework enthält ein reichhaltiges Objektmodell für Objekte, die in anderen Application Domains existieren. Hier erfahren Sie, wie man über das tcp- und über das http-Protokoll eine Art eigener Webservices programmieren kann.

* * *

Auf dieser Seite

Grundlagen des Remoting in Microsoft .NET Grundlagen des Remoting in Microsoft .NET
.NET Remoting Begriffe .NET Remoting Begriffe
.NET Remoting Objekte .NET Remoting Objekte
Marshalling Sample: Marshalling Sample:
User Binary Formatter Beispiel: User Binary Formatter Beispiel:

Grundlagen des Remoting in Microsoft .NET

In der klassischen großen 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 seien hier kurz erwähnt: Zunächst wird natürlich ein Großteil des Geschäftsprozesses gekapselt und wiederverwendbar gemacht. Außerdem wird natürlich auch die vorhandene Firmeninfrastruktur verborgen oder sie sollte es zumindest sein. Das .NET Framework bietet eine eigene Klassensammlung für Remoting. Für diejenigen unter Ihnen, die von Remoting noch nicht viel gehört haben, kommen hier eine kurze Übersicht und Tipps, wo man so etwas einsetzten könnte. Remoting ermöglicht es Anwendungen untereinander zu kommunizieren (Datenaustausch in Form von Objekten), egal ob sie auf dem selben Computer laufen, über ein Netzwerk verteilt sind oder auf verschiedenen Plattformen laufen. Das Microsoft .NET Framework enthält ein reichhaltiges Objektmodell für Objekte, die in anderen Application Domains 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 neu in der "programmers world". Als AppDomain kann man so etwas wie einen Windows 32 Prozess sehen. Im Gegensatz dazu ist eine AppDomain jedoch nicht an das Windows-Plattform-spezifische Prozess-Thread-Modell gebunden.
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-Konzepts. Diese Objekte helfen uns, überhaupt einen Austausch mit anderen Systemen und deren Objekten 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äßig 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 die Kommunikation mit einem Remote-Objekt ermöglicht (mit "messages", die durch einen "Formatter" behandelt werden und über "channels" verschickt werden). Das ObjRef Objekt wird also benutzt um eine Objek- Referenz über AppDomain-Grenzen hinweg zu transferieren. Das Erzeugen einer ObjRef wird auch als Marshaling bezeichnet. Das ObjRef Objekt enthält Informationen, welche den Typ des Objektes, das "gemarshaled" wird, beschreiben, sowie Informationen, wo dieses Objekt zu finden und was für die Kommunikation wichtig ist, wie zum Beispiel die Kommunikation von statten gehen soll.
Marshalling: Als Marshalling bezeichnet man den Vorgang, wenn eine ObjRef aus einem Proxy erzeugt wird.
UnMarshalling: Als UnMarshalling 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 Szenarien sinnvoll, in denen 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 müssen außerdem noch für "load balancing" konfiguriert werden.
Singelton Objects: Singelton Objects sind Objekte, die mehrere Clients bedienen können und folglich "State"-Informationen" und Daten zwischen Client-Anforderungen "sharen". Solche Objekte sind sinnvoll in Szenarien, in denen Daten explizit zwischen Clients "geshared" werden müssen und das Erzeugen solcher Objekte mit einem erheblichen Overhead verbunden ist.
Client-Activated Objects: Client Activated Objects sind serverseitige 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, welche das ObjRef verwendet. Der Aufruf der Methoden wird durch den Proxy selbst abgearbeitet. Clientseitige Objekte können "State"-Informationen für diesen einen speziellen Client speichern, jedoch nicht für verschiedene Clients. Jede clientseitige Aktivierung mit dem "new" Operator gibt einen Proxy mit einer unabhängigen Instanz des Server zurück.
So, nun haben wir einen Großteil 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, es ist 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:
    myObj.myNestedObject

Weiterhin haben Sie die Möglichkeit, Objekte ByValue zu marshalen (MBV) oder ByReference zu marshalen (MBR). Bei Objekten, die per MBV remotet werden, wird eine komplette Kopie des Objekts 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 werden:

System.Runtime.Remoting  
System.Runtime.Remoting.Channels

 

Marshalling 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());  
}  
}

 

User 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.

Bild01

Zuerst erstellen wir 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.
Im Anschluss werden der Klasse zwei Methoden hinzugefügt, 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 registriert einen neuen TCP Channel auf Port 8089.

TcpChannel myChannel = new TcpChannel(8089); 
... 
ChannelServices.RegisterChannel(myChannel);

Danach muss 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. Dass das natürlich nicht die feine "Englische" ist, ist mir klar, aber wer will, kann das Ganze 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. Außerdem haben wir in .NET immer noch das Problem, dass wir, wie in WinDNA, das Server-Objekt (bzw. früher die *.tlb) zur 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 bisschen 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, denn den kennt der Server.

... 
TcpChannel myClientChannel = new TcpChannel(); 
ChannelServices.RegisterChannel(myClientChannel); 
...

Anschließend erstelle ich eine Instanz des Remote Objekts HelloServer, wobei ich bei Aufruf der Methode Activator.GetObject() die Adresse mitgeben muss, an der 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!