Dit is het eerste deel van een serie artikelen over het bouwen en gebruiken van .NET Remoting waarmee het mogelijk is om objecten over het netwerk aan te spreken. De .NET Remoting infrastructuur geeft de ontwikkelaar een eenvoudig framework waarmee objecten die niet fysiek op de cliëntmachine staan, aangesproken kunnen worden. Zo kunnen gedistribueerde applicaties gebouwd worden.
.NET Remoting maakt gebruik van TCP en HTTP kanalen om te communiceren tussen de server en de client. In de komende artikelen kijken we naar beide vormen van communicatie. Ook een Windows Service staat op het programma.
Wat is Remoting?
Met .NET Remoting is de ontwikkelaar in staat applicaties te bouwen waarin methoden worden aangeroepen van objecten over het netwerk op een zelfde manier als wanneer deze objecten lokaal, dat wil zeggen op de zelfde plek als het aanroepende object, beschikbaar zouden zijn. Om dit mogelijk te maken moeten we beschikken over een proxy. Zo'n proxy is de lokale vertegenwoordiger van het 'remote' object. De proxy zorgt er voor dat alle verzoeken aan het remote object worden doorgestuurd en dat het antwoord weer teruggegeven wordt aan het object dat het verzoek had gedaan.
Application Domains en Remoting
Klassieke Win32 applicaties zijn tijdens hun uitvoering geïsoleerd van elkaar, omdat iedere applicatie in een ander proces draait. De scheidslijn tussen de verschillende processen zorgt er voor dat geheugen en andere resources die het ene proces in gebruik heeft, niet tegelijkertijd kunnen worden aangesproken door het andere proces. Ook als er een fout optreedt, blijft die fout beperkt tot de applicatie in dat proces zelf. In .NET wordt de overhead door het switchen tussen de verschillende processen wat verminderd door een nieuwe, lichte vorm van uitvoering te introduceren: het application domain. Als je een .NET applicatie uitvoert, creëert de CLR een application domain waarin de assemblies worden geladen. Het is niet zo dat het Win32 proces verdwenen is; het Win32 proces kan wel één of meerdere application domains bevatten.
Vanwaar deze introductie van application domains? Het is namelijk zo dat in .NET een remote object gedefinieerd wordt als een object dat zich in een ander application domain bevindt dan het domain van de 'caller' van het object. Het is dus niet noodzakelijk dat het remote object echt op een andere machine aanwezig is, of zelfs in een ander proces. Je gebruikt .NET Remoting al om objecten te kunnen gebruiken uit een ander application domain.
Marshalling
Om een object toegankelijk te maken buiten het eigen application domain moet de remoting omgeving een mechanisme hebben om objecten, of object-referenties, te transporteren tussen domains. Dat gebeurt via het zogenaamde marshalling mechanisme waar we twee vormen van kennen:
- Marshalling by reference: in dit geval wordt er een referentie gecreëerd naar het remote object. Hiermee creëert het aanroepende domain een proxy object dat fungeert als de lokale vertegenwoordiger van het remote object. De proxy zorgt voor het aannemen en doorsturen van verzoeken naar het remote object. Om dit mogelijk te maken, moeten de remote objecten erven van System.MarshalByRefObject.
- Marshalling by value - hier wordt een kopie van het object gemaakt, geserialiseerd, en getransporteerd naar het andere domain waar het weer wordt opgebouwd. Dit is de standaardmanier waarop typen als parameters worden doorgestuurd naar een remote methode, en voor de typen die dan weer terugkomen. Objecten die by value worden doorgegeven, moeten serialiseerbaar zijn. Als je bijvoorbeeld een eigen class hebt gemaakt, dan moet deze class de ISerializable interface implementeren of moet het object met het Serializable attribuut aangemerkt worden.
Remote objecten hosten
Een remote object moet gehost worden in een server applicatie. Dat betekent dat het remote object alleen beschikbaar is, wanneer deze server actief is. Zo'n server kan in zijn meest eenvoudige vorm een .NET managed executable zijn die niets anders doet dan het registreren van het object in de .NET Remoting infrastructuur. Je kunt ook remote objecten hosten in COM+ Services of in Internet Information Server. In een volgend artikel gaan we kijken naar de hosting van een remote object in IIS.
De taak van de hosting server is het beschikbaar maken van remote objecten via het netwerk door deze te registreren in de remoting infrastructuur. Om dat voor elkaar te krijgen moet het uniek identificeerbaar zijn via een URI (Uniform Resource Identifier). De URI identificeert ieder object door een unieke naam,een kanaal en een endpoint (de naam van de machine en de poort). Deze informatie wordt door de clients gebruikt om het remote object te vinden en aan te roepen. In de volgende figuur zie je een voorbeeld van een remote service (RemoteService1) met drie remote objecten.

Fig. 1: een remote service
De bovenstaande service staat op 'DeServer' en luistert op TCP poort 9389 naar binnenkomende verzoeken. Er zijn drie objecten beschikbaar in deze service: RemoteObject1, RemoteObject2 en RemoteObject3.
Het maken van een remote service
Oké, genoeg theorie. Laten we eens kijken hoe je een remote service zelf kunt maken. In het voorbeeld maken we een service met de naam PassService met een object dat kijkt of een gebruiker met een userid en wachtwoord toegang krijgt. Het enige dat het object doet, is het accepteren van een userid en een wachtwoord en aan de hand daarvan geeft hij een bericht terug of het userid en wachtwoord akkoord zijn. Let op: het is niet nodig dat de client en de server op twee verschillende machines zitten. We zetten wel het remote object en de service in een aparte assembly. Het is namelijk van belang dat zowel de client als de server bekend zijn met het remote object. Dat lijkt onhandig, omdat de client tijdens compile time moet beschikken over een assembly waar het object in zit. Dat is ook onhandig, maar voorlopig doen we het even zo. In een volgend artikel worden alternatieven besproken.
De service is tamelijk eenvoudig om het principe van remoting te kunnen uitleggen. We gaan straks nog andere versie van de service maken, dus de implementatie van het remote object wordt zo simpel mogelijk gehouden.
Eerst roepen we de juiste namespaces aan voor onze service. We gebruiken voor het voorbeeld Visual Basic.NET code [redactie: door de SDGN zijn de voorbeelden ook herschreven voor C#]. Als je de onderstaande regels invoert, zul je merken dat de laatste regel niet herkend wordt. Dat komt omdat je in je Visual Studio.NET project ook een referentie naar de System.Runtime.Remoting.dll assembly moet opnemen.
VB.NET
Imports System
Imports System.Runtime.Remoting
Imports System.Runtime.Remoting.Channels
Imports System.Runtime.Remoting.Channels.Tcp
C#
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
De class waarin de methode is opgenomen voor het controleren van een userid en wachtwoord staat hieronder. Dit is de class voor ons remote object.
VB.NET
Public Class PassObj : Inherits MarshalByRefObject
Public Sub New()
Console.WriteLine("Pass Object Geactiveerd")
End Sub
Public Function CheckPass(ByVal userid As String, ByVal password As String)_
As String
Dim UserAcceptance As String
Dim ReturnValue As String
If userid = "admin" And password = "welkom" Then
UserAcceptance = "Admin aangemeld op systeem."
ReturnValue = "aangemeld"
Else
UserAcceptance = "Toegang geweigerd voor: '" & userid & _
"' met wachtwoord '" & password & "'"
ReturnValue = "geweigerd"
End If
Console.WriteLine(Now.ToLongTimeString & ": " & UserAcceptance)
Return ReturnValue
End Function
End Class
C#
public class PassObj : MarshalByRefObject
{
public PassObj()
{
Console.WriteLine("Pass Object Geactiveerd");
}
public string CheckPass( string userid, string password )
{
string UserAcceptance;
string ReturnValue;
if (userid == "admin" && password == "welkom")
{
UserAcceptance = "Admin aangemeld op systeem.";
ReturnValue = "aangemeld";
}
else
{
UserAcceptance = "Toegang geweigerd voor: '" + userid +
"' met wachtwoord '" + password + "'";
ReturnValue = "geweigerd";
}
Console.WriteLine(Now.ToLongTimeString + ": " + UserAcceptance);
return ReturnValue;
}
}
De server is geïmplementeerd in de PassService class. De main-methode van deze class meldt het PassObj aan in de remoting infrastructuur. Met de regel
ChannelServices.RegisterChannel(New TcpChannel(9000))
geven we aan dat de service gaat luisteren op TCP poort 9000.
Houdt er overigens rekening mee dat poorten onder de 1024 geregistreerde poorten zijn met een internationaal afgesproken betekenis. Neem dus altijd een poort boven de 1024 en onder de 65000 (het maximum).
We gebruiken
RemotingConfiguration.RegisterWellKnownServiceType
om het object remote beschikbaar te maken. De benodigde parameters zijn hier het type van het remote object, de naam en de manier waarop het object geactiveerd moet worden. In ons voorbeeld kiezen we voor een object dat in Singleton mode wordt geactiveerd. Dat wil zeggen dat één enkel object gecreëerd wordt, dat gedeeld kan worden door meerdere clients. In een volgend artikel gaan we naar alternatieven kijken.
We hebben nu het object beschikbaar gemaakt via tcp://localhost:9000/PassObj. Via ENTER wordt de server gestopt en is het object niet meer beschikbaar voor clients.
VB.NET
Imports System
Imports System.Runtime.Remoting
Imports System.Runtime.Remoting.Channels
Imports System.Runtime.Remoting.Channels.Tcp
Imports PassObject
Public Class PassService
Public Shared Sub Main()
'we registreren een kanaal
ChannelServices.RegisterChannel(New TcpChannel(9000))
'we melden PassObj aan bij de remoting services
Console.WriteLine("Aanmelden PassObj als Singleton...")
RemotingConfiguration.RegisterWellKnownServiceType( _
GetType(PassObj), _
"PassObj", _
WellKnownObjectMode.Singleton)
Console.WriteLine("We wachten op remote calls")
Console.WriteLine("Druk op ENTER om af te sluiten")
Console.ReadLine()
End Sub
End Class
C#
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using PassObject;
public class PassService
{
[STAThread]
static void Main()
{
//we registreren een kanaal
ChannelServices.RegisterChannel( new TcpChannel(9000) );
//we melden PassObj aan bij de remoting services
Console.WriteLine("Aanmelden PassObj als Singleton...");
RemotingConfiguration.RegisterWellKnownServiceType(
GetType(PassObj),
"PassObj",
WellKnownObjectMode.Singleton);
Console.WriteLine("We wachten op remote calls");
Console.WriteLine("Druk op ENTER om af te sluiten");
Console.ReadLine();
}
}
De remoting client
In de volgende code zie je de client die gebruik gaat maken van het remote object.
VB.NET
Imports System
Imports System.Runtime.Remoting
Imports System.Runtime.Remoting.Channels
Imports System.Runtime.Remoting.Channels.Tcp
Imports PassObject
Public Class PassClient
Public Shared Sub Main()
ChannelServices.RegisterChannel(New TcpChannel())
Dim passObjRef As PassObj = CType(Activator.GetObject( _
GetType(PassObj), _
"tcp://localhost:9000/PassObj"), PassObj)
Console.WriteLine("Piet: " & passObjRef.CheckPass("piet", "puk"))
Console.WriteLine("admin: " & passObjRef.CheckPass("admin", "welkom"))
End Sub
End Class
C#
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using PassObject;
public class PassClient
{
[STAThread]
static void Main()
{
ChannelServices.RegisterChannel(new TcpChannel());
PassObj passObjRef = CType(Activator.GetObject(
GetType(PassObj),
"tcp://localhost:9000/PassObj"), PassObj);
Console.WriteLine("Piet: " + passObjRef.CheckPass("piet", "puk"));
Console.WriteLine("admin: " + passObjRef.CheckPass("admin", "welkom"));
}
}
De client registreert een nieuw TCP kanaal en roept Activator.GetObject aan. In deze methode wordt het type van het remote object meegegeven, en ook de URI. Daarna wordt de CheckPass methode aangesproken en sluit de client weer af.
Testen
Om onze remote service te kunnen testen openen we twee commando prompts. In de eerste commando prompt starten we de server.

Vervolgens starten we in de tweede commando prompt de client.

Je ziet na het starten direct resultaat. We kunnen verifiëren of de service inderdaad is aangesproken door een blik te werpen op ons eerste commando prompt scherm.

Het is goed om te constateren dat het PassObj slechts één maal gecreëerd is. De methode Activator.GetObject zorgt dus niet voor het creëren van het object. We kunnen daarnaast opmerken dat de state van het object op de server wordt bijgehouden. Wanneer we een nieuwe client zouden starten, dan zou deze client nog steeds van hetzelfde object gebruik maken.
Conclusies
.NET Remoting is een krachtig middel om gedistribueerde applicaties te ontwikkelen. De architectuur is uitbreidbaar, zodat ondersteuning van aanvullende protocollen en transporten kan worden toegevoegd. Als je een beter gekoppeld, op objecten gebaseerd programmeermodel tussen client en server nodig hebt, gebruik dan .NET Remoting. .NET Remoting biedt toegang op afstand tot objecten op de server met een volledige type safety.
In de komende tijd verschijnen meer artikelen over .NET Remoting. Er zijn namelijk wel enkele hobbels die je moet overwinnen om remoting objecten op een efficiënte en betrouwbare manier te gebruiken in applicaties. Als je daar niet op kunt wachten, zijn hier al vast wat links naar nuttige informatie over .NET Remoting.
Nuttige Links