By Luke Venediger
Abstract
Remoting is an infrastructure that allows the developer to use remote objects. Remote
objects are objects that are based (or instantiated) outside of the caller's Application
Domain. This example shows you how to use both remote object access mechanisms
(pass by value and pass by reference). It also shows you the power of remoting for
distributed computing with a simple yet powerful implementation of a Task Server.
The Task Server can take any object that implements the simple ITask interface, and run it
within its own Application Domain. What's more, it can accept tasks from multiple clients at
one time.
This tutorial requires that you have had exposure to C#. It does not require that you have
used C# Remoting.
At the end of the tutorial you will be able to:
- Set up a client / server object relationship
- Pass an object by value
- Pass an object by reference
- Understand the concepts of remote task distribution
Remote Objects
Remote objects are either passed by reference or passed as an entire object.
In the first instance, the object's reference is passed from App Domain 'A' to App Domain
'B', but the object method calls are marshalled between 'A' and 'B'. The object lives and
operated on App Domain 'A', but appears to App Domain 'B' to be a locally instantiated
object.
In the second instance, the entire object and it's dependencies (known as the Object
Graph or Web) is serialized into byte-form and literally delivered from App Domain 'A' to
App Domain 'B'. The object is then unserialized and brought to life on App Domain 'B'. The
object now lives and operates locally on App Domain 'B'.
Two areas need to be attended to, namely the server Application Domain (i.e. the object
host) and the client (i.e. the object client).
Setting up the Object Host, known as the Server
The first step to be taken when setting up the server is to create a Channel through which
your object will communicate. This can be either a TCP/IP channel or an HTTP channel.
TCP channels are faster and recommended for networks that have limited firewall
restrictions on packets traversing through networks. HTTP channels, however, are far
more flexible and are recommended for environments where remoting is done over broad
networks such as the Internet.
We will be using a TCP/IP channel, and will run the client and the server on the same
machine, under two different application domains. Thus, to create my channel on port
8065 on my local TCP/IP stack I type the following code:
TcpChannel myChannel = new TcpChannel(8065);
The next step is to register the channel with .NET's Channel Services. This will make the channel
publicly accessable outside of the server's app domain. To do this I type:
ChannelServices.RegisterChannel(myChannel);
The last step is to tell the .NET remoting infrastructure about the object that we want to
make public. We need to give the object type and location, a name by which clients will
locate the object and the way in which the remoting infrastructure must handle calls made
to the object. To get the object type I do the following (rather unconventional, but makes
more sense to me):
Type objectType = new MyCoolObject().GetType()
To register the object with the .NET remoting infrastructure I type the following:
RemotingConfiguration.RegisterWellKnownServiceType(
objectType, "MyCoolObject",
WellKnownObjectMode.Singleton);
There are two ways an object call can be handled: Singleton and SingleCall. Singleton
implies that the object is created on the first client method call and kept alive until the client
breaks the connection or the object exits naturally. SingleCall implies that the object is
created on every client method call, is alive for the duration of the method call and then
exits once the method finishes. The client connection does not end with a SingleCall, only
the object is destroyed.
Setting up the client for the remote object
The first requirement on the client side is that the compiled class for the remote object is
available locally. This is used by the .NET remoting infrastructure proxy to intercept and
marshal messages being passed to and from the remote object.
A channel needs to be created once again, and then registered with the infrastructure so that it can
become useable:
TcpChannel myChannel = new TcpChannel();
ChannelServices.RegisterChannel(myChannel);
Note that we don't specify a port address when we create the channel. This is specified in
the following step, when we ask the server to give us a reference to the remote object that
we are after. The code:
MyCoolObject mine = (MyCoolObject)Activator.GetObject(
typeof(MyCoolObject),
"tcp://localhost:8085/MyCoolObject");
The first parameter gets the type of the object we are trying to locate. The second parameter
specifies the URL of the remote object. Once we get the object we need to type-cast it to a
MyCoolObject from a generic object.
We now have a reference to the live object on the server, which we can use as if it were within our
local app domain.
The All Powerful Task Server
The Task Server is an explicit example of how one can use .NET's pass-by-value object remoting
mechanisms. The Task Server has an object called the TaskRunner which a client can get a
reference to. The TaskRunner is designed to take in any object from the client that implements the
ITask interface.
The ITask interface enforces that clients create their task objects with the methods Run() and
Identify(). Clients can then create a complex and resource-intensive task that implements the ITask
interface and submit it to the Task Server for execution within his application domain. This means
that clients that do not have adequet computational resources to execute their complex task can
make use of the Task Server's massive resource pool to do the work for them.
So, a client who would like to work out Pi with 3000 decimal accuracy can create an object that
performs the calculation and implements the ITask interface. The task is then submitted using the
referenced TaskRunner object to the Task Server for execution.
The ITask interface enforces that clients return an object from their Run() method. This can,
ofcourse, be a null value but it also allows for the client to return an intelligent encapsulated
result.
In a nutshell, remoting using pass-by-value is best for situations where the server does not have an
explicit representation of the object that the client would like to execute in a remote context.
Passing Objects by Value
When an object is to be passed by value, it needs to be converted into a transportable
form. A live object instantiated within an application domain takes up a piece of system
memory and is able to respond to messages send to it from other objects. To send this
object to another application domain the object must be serialized. This means that it must
be broken down into a collection of bytes that can be used to create a clone of the object
within the destination app domain.
To do this, you will use the Serializable compiler attribute. This instructs the compiler to
serialize the following construct. You can flag an entire class as Serializable, or only parts
of it including a selected number of methods. In the Task Server environment, the entire
client task object must be Serializable. This is done as follows:
Using System;
[Serializable()]
class ClientTask {
Note that if an object has dependencies, such as an outside library, that is used by the
object then the parts of the library that are used must also be serializable. This is a direct
implication of the serialization process and is required to build up the object in the remote
app domain. These dependencies are referred to as the object graph.
Running the Example
This example has been written and tested in C# for .NET Beta 2
To use the Task Server, you must compile the code in the following order
csc /target:library /out:ITask.dll ITask.cs
csc /target:library /out:TaskRunner.dll TaskRunner.cs
csc /r:ITask.dll /r:TaskRunner.dll TaskServer.cs
csc /r:ITask.dll /r:TaskRunner.dll TaskClient.cs
The order is relevant because the TaskServer and TaskClient classes are dependent on
the TaskRunner and ITask libraries.
To run the example, first start the server and wait until you see:
[i] Task Server Started, press to exit
Then you can run the client application.
Inside the TaskClient you will find a class that implements the ITask interface and is
serializable. This task will multiply two numbers together and return an 'int' object. I
encourage you to create your own task and try running it on the Task Server. This will give
you a first-hand feel for the power of this form of Distributed Computing. It essentially
allows client developers to make use of the abundant resources available on the Task
Server's machine to perform complex and intensive tasks.
You can use the Task Server to perform operations like number-crunching, compression,
encryption, sorting the list is endless.
The Source Code
ITask.cs - The ITask Interface
TaskRunner.cs - The Task Runner Library
TaskServer.cs - The Task Server
TaskClient.cs - The Client Application
Remoting-Source.zip - All the source files zipped