Remoting
From MetaSharp
Article Author(s): Audric Thevenet
All Rights Reserved.
Contents |
Introduction
The .NET Remoting is a network protocol giving the ability for your application to create objects on distant computers as if they were created on your local computer.
Basically you could do it in 3 projects (client/server/remoteobject) but it's not very clean. It's not clean because the client and server would have the object definition. This is why in this simple example of dotnet remoting I choosed to have 4 project (client/server/object/interface).
Download VS2005 Remoting Sample
Remote Object Interface
We need 2 interfaces here, the remote object interface:
namespace RemoteObjectNS
{
public interface IRemoteObject
{
int GetAnInteger();
}
}
and the remote object factory interface:
namespace RemoteObjectNS
{
public interface IRemoteObjectFactory
{
IRemoteObject NewRemoteObject();
}
}
The object interface holds a list of the remote object methods you might want to call with remoting. The factory interface holds constructor(s) of your remote object. If we had no factory we could only have 1 constructor for the remote object. That being said, you can use the same factory to build all your remoting objects of any kinds. Each type of object would by the way have his own interface.
Remote Object
Our remote object of course implement the interface. I also added a little debug message in the constructor. Pretty simple.
using System;
namespace RemoteObjectNS
{
public class RemoteObject : MarshalByRefObject, IRemoteObject
{
public RemoteObject()
{
Console.WriteLine("[RemoteObject] creation of server-side object");
}
#region IRemoteObject
public int GetAnInteger()
{
Console.WriteLine("[RemoteObject] GetAnInteger() ran here");
return 10;
}
#endregion
}
}
2 things to note:
- I added MarshalByRefObject to set our class behavior through remoting
- I defined my RemoteObject class in the same namespace as the interface: RemoteObjectNS
Now let's have a look at the factory:
using System;
using RemoteObjectNS;
namespace RemoteObjectNS
{
public class RemoteObjectFactory : MarshalByRefObject, IRemoteObjectFactory
{
private RemoteObject singleton;
public RemoteObjectFactory()
{
Console.WriteLine("[RemoteObjectFactory] creation of server-side factory");
}
#region IRemoteObjectFactory
public IRemoteObject NewRemoteObject()
{
Console.WriteLine("[RemoteObjectFactory] NewRemoteObject() ran here");
if (singleton == null)
singleton = new RemoteObject();
return singleton;
// if not singleton object
// return new RemoteObject();
}
#endregion
}
}
Same behavior as the RemoteObject, though you can see I added a singleton design pattern for the remote object. Note that I also added the little line of code in case you prefer it not to be a singleton. Pretty simple too.
Server
The server itself is a very simple program, that needs 2 references:
- IRemoteObject project (this includes the 2 interfaces: object and factory)
- RemoteObject project (this includes the 2 classes: object and factory)
using System;
using System.Runtime.Remoting;
namespace ServerNS
{
public class Program
{
[STAThread]
static void Main()
{
RemotingConfiguration.Configure("Server.config", false);
Console.WriteLine("Press return to end the server");
Console.ReadLine();
}
}
}
As you see it's totally automated through the use of a configuration file that can be modified at a later time to adjust the server behavior.
Let's see how this XML configuration file looks like:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.runtime.remoting>
<application name="Server">
<service>
<wellknown type="RemoteObjectNS.RemoteObjectFactory,RemoteObject" mode="Singleton" objectUri="RemoteObjectFactory" />
</service>
<channels>
<channel port="65100" ref="http" />
</channels>
</application>
</system.runtime.remoting>
</configuration>
As you see, we make both objects types known but we expose the URI of the factory only. We also specify the reference method (http) and the port of remoting service exposition.
Client
The client program is a bit less simple, but it needs only 1 reference:
- IRemoteObject project (this includes the 2 interfaces: object and factory)
This way, the client does not need to have the code of the remote object, it's a lot safer this way. The source code looks a lot like the server one:
using System;
using System.Runtime.Remoting;
using RemoteObjectNS;
namespace ClientNS
{
public class Program
{
[STAThread]
static void Main()
{
RemotingConfiguration.Configure("Client.config", false);
IRemoteObjectFactory f = (IRemoteObjectFactory) Activator.GetObject(typeof(IRemoteObjectFactory));
IRemoteObject o = f.NewRemoteObject();
Console.WriteLine(o.GetAnInteger().ToString());
Console.WriteLine(o.GetAnInteger().ToString());
Console.WriteLine(o.GetAnInteger().ToString());
Console.WriteLine("Press return to end the client");
Console.ReadLine();
}
}
}
As you see it's nearly the same as the server. The hard part is the creation of the factory. By this line will only be present once in your program. Once done, all object are instanciated in a very familiar way through this factory object.
Before detailling te activator, let's see the client XML configuration file:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.runtime.remoting>
<application name="Client">
<client>
<wellknown type="RemoteObjectNS.IRemoteObjectFactory,IRemoteObject" url="http://localhost:65100/RemoteObjectFactory" />
</client>
</application>
</system.runtime.remoting>
</configuration>
Client side, only the interfaces are made known (of course the client does not have the real classes code), and only the factory is reached through the remoting at the ip (localhost here) and port of your choice (configured in server). Pretty easy too.
Now the hard part, lets look at the activator, a bit complicated at first sight, but not that much in fact:
using System;
using System.Collections;
using System.Runtime.Remoting;
namespace ClientNS
{
public class Activator
{
private static bool initialized;
private static Hashtable roTypes;
public static object GetObject(Type type)
{
if (!initialized)
InitRoTypes();
WellKnownClientTypeEntry entry = (WellKnownClientTypeEntry)roTypes[type];
return System.Activator.GetObject(entry.ObjectType, entry.ObjectUrl);
}
private static void InitRoTypes()
{
initialized = true;
roTypes = new Hashtable();
foreach (WellKnownClientTypeEntry entry in RemotingConfiguration.GetRegisteredWellKnownClientTypes())
roTypes.Add(entry.ObjectType, entry);
}
}
}
First time you call GetObject, it'll initialize through InitRoTypes all the "known" types through the remoting. Then it'll return you the type from the hashtable created during initialization and use it to create a client-side proxy object. This way you can use this object as if it was a true object but everything you'll do will be redirected through the remoting protocol to your distant server for processing.
Conclusion
Run, the server, then run the client. Here's what you'll get.
Server output:
Press return to end the server [RemoteObjectFactory] creation of server-side factory [RemoteObjectFactory] NewRemoteObject() ran here [RemoteObject] creation of server-side object [RemoteObject] GetAnInteger() ran here [RemoteObject] GetAnInteger() ran here [RemoteObject] GetAnInteger() ran here
Client Output:
10 10 10 Press return to end the client
As we see, all the code was run server side, even though we used it as if it was client side. This is Remoting.
In Depth Remoting
If you wish to dig a bit deeper, you can give a try with the following links:
