Vous êtes sur la page 1sur 18

Remoting

We embark upon this chapter by initiating the concept of 'remoting' and attempting to unravel it by using a
diminutive example. In 'remoting', there exists a 'client' program on one machine, which may be located anywhere
in the world, and a 'server' program on another machine. The client program thereafter, calls a function in the
server program. The server, after having executed the function, delivers the return value of the function, back to
the client.

To effect and bring the abovementioned scenario to fruition, we have employed three programs named o.cs, s.cs
and c.cs. Furthermore, to avoid calling the compiler command for each one of them individually, we have placed
the compile commands in a batch file named a.bat.

o.cs
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
public class zzz : MarshalByRefObject
{
public zzz() {
Console.WriteLine("Constructor");
}
~zzz() {
Console.WriteLine("Destructor");
}
public String abc() {
Console.WriteLine("Vijay Mukhi2");
return "Sonal";
}
}

s.cs
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
public class sss {
public static void Main()
{
TcpChannel c = new TcpChannel(8085);
ChannelServices.RegisterChannel(c);
RemotingConfiguration.RegisterWellKnownServiceType(
Type.GetType("zzz,o"), "eee", WellKnownObjectMode.SingleCall);
System.Console.WriteLine("Press <enter> to exit...");
System.Console.ReadLine();
}
}

c.cs
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
public class ccc
{
public static void Main()
{
TcpChannel c = new TcpChannel();
ChannelServices.RegisterChannel(c);
zzz a = (zzz)Activator.GetObject(typeof(zzz), "tcp://localhost:8085/eee");

1
if (a == null)
System.Console.WriteLine("Could not locate server");
else
Console.WriteLine(a.abc());
}
}

a.bat
csc.exe /t:library o.cs
csc.exe s.cs
csc.exe /r:o.dll c.cs

Output in server dos box


Press <enter> to exit...
Constructor
Constructor
Vijay Mukhi2

Destructor
Destructor

Output in client dos box


Sonal

We simulate the 'remoting' behaviour by bringing two dos boxes into play. We run the client program in one box
and the server program in the other box.

The file o.cs is created with a class zzz, which derives from the class MarshalByRefObject. This abstract class,
belonging to the System namespace, cannot be used in isolation. Hence, it requires a class that derives from it. A
class of this kind permits remote objects to call code from it. There exist a constructor and destructor, which
furnish information regarding the time of creation and destruction of an object.

A function called abc is created, which displays 'Vijay Mukhi2' by using the WriteLine function and then, returns
the name of 'Sonal'. Other programs can call the function abc, merely by deriving a class from
MarshalByRefObject. The file o.cs is compiled into a dll, using the /target:library option. Understanding the
abovementioned explanation does not require the genius of a rocket scientist.

We shall now go ahead and write a simple server program named s.cs.

c is an object, which is an instance of class TcpChannel, in the namespace


System.Runtime.Remoting.Channels.Tcp. The constructor is assigned a port number, on which, the other
machine can establish communication with it. The protocol used here is known as TCP/IP, where TCP stands for
Transmission Control Protocol and IP implies Internet Protocol.

The port number facilitates the creation of a channel between two machines. The number that we have selected
for the port is 8085. This is because all the samples provided by Microsoft use this number. It must obviously be
a lucky mascot for that Company. You may select any other number, as long the same number is used for the
client and the server. Hereinafter, whenever a client connects on port 8085, the TCP/IP stack or the Windows
Internet Software keeps the server apprised about the connection. The server, in turn, keeps a listening vigil on
port 8085.

The namespace, System.Runtime.Remoting contains a class ChannelServices, which has a static function
RegisterChannel. This function accepts an IChannel interface as a parameter and thereafter, registers the
Channel object with the channel services. The main activities of this function are not known. It suffices to say
that, without this function, the TCPChannel object that has been created, would not be effective. The class
TCPChannel derives from a large number of interfaces, such as IChannelSender, IChannel, IChannelReceiver,
IDictionary, ICollection and IEnumerable.

2
The most imperative function that the server has to call is, the one that registers the function abc in class zzz,
contained in the file o.dll. The name of this static function is RegisterWellKnownServiceType, of the class
RemotingConfiguration, belonging to the namespace System.Runtime.Remoting.

This function takes three parameters, beginning with a type object that represents the name of the assembly that
contains the object to be registered. Our object is located in a dll. It could even be located in an executable. The
name 'o' of the dll file is passed as the second parameter to the GetType Object. The first parameter to this
function is the name of the class that represents the object. In case the class is located in a namespace, the full
name has to be furnished. The GetType object returns a Type object.

The second parameter of the function RegisterWellKnownServiceType, represents a URI, or a Universal Resource
Identifier. It could be any original word that helps in identification of functions in the class zzz. This is referred to
as an 'endpoint', where the object shall be published. The client does not connect to the class zzz directly, but to
an endpoint. In our case, the URI is eee. It points indirectly to the class zzz, which is the object that needs to be
remoted. Any string may be used, provided the same string is used by the client too. Had we wished to connect
using ASP.Net, we would have been compelled to use zzz.soap, as the endpoint.

The last parameter specifies the mode, which may assume any of the two values, i.e. SingleCall or Singleton. The
data type is an enum named WellKnownObjectMode, which only embodies the two abovementioned values. The
mode specifies the 'lifetime' of the object, i.e. the frequency of its creation. If we specify the mode as SingleCall, a
new instance of the object zzz will be created each time a message is received. If it is assigned the value of
Singleton, an instance of zzz will be created only once, and all the clients shall interact with it.

On compiling s.cs, an executable named s.exe will be generated.

The client code, which calls code from the remote object is placed in the file c.cs. In this file, we commence with
the 'using' statement, in order to introduce the namespaces for the classes. Thereafter, a TcpChannel is created,
but unlike a server, it is devoid of a port number. The port number assigned to the client is of no significance,
since it is not binding on the client to listen to any request. Therefore, a random value is assigned as the port
number. RegisterChannel registers the TcpChannel c.

Next, we use the static function GetObject from the Activator class in the system namespace. This function
accepts two parameters:
• The first parameter is of data type Type, which represents the class that we wish to instantiate.
• The second parameter is the URI of the object, which is to be instantiated.

The URI follows a specific sequence, which is as follows:


• It commences with the protocol that is required for communication. In our case, it is tcp.
• This is followed by the machine name on which the server resides.

In our case, everything is located on a single machine. Hence, we use the machine name 'localhost'. The machine
name is by suffixed with a colon.

• This is succeeded by the port number.


• Finally, the endpoint created in the server is to be specified. In this case, it is eee.

The abovementioned sequence has to be maintained, and all the above items are mandatory.

This function returns an object that is an instance of class zzz, which we store in object 'a'. If the object 'a' has a
value of null, it generates an error and displays a suitable error message. Otherwise, the function abc of the
object 'a' is called, and the return value is displayed using the WriteLine function.

On compiling c.cs, an error is generated. So, we provide a reference to the dll named o.dll, since the class zzz is
present in it.

In one of the dos boxes, we first run the server 's'. On the surface of it, it appears as though only the string "Press
<enter> to exit" has been displayed. But in the background, the server has been registered as a sink for all those
functions that need to call the function abc, from its endpoint eee.

3
If we merely press the Enter key, the ReadLine function, whose raison d'être is to prevent our server from quitting
out, actually exits.

When we run the client in a separate dos box, the server dos box displays 'Constructor' twice. This establishes
the fact that the server has located the class zzz in the assembly o.dll and instantiated it. The server instantiates
the object that is to be remoted, to enable the framework to read the metadata present in the assembly. This is
part of its registration process.

Thereafter, it displays the string 'VijayMukhi 2' in the server dos box, since the client has called the function abc.
The client dos box displays 'Sonal', which is the return value of the function.

The client program quits out gracefully, whereas, the server cools its heels, waiting for other calls. The server
destroys the current object, and the framework keeps a listening vigil for other clients, which may be eager to
connect on previously registered channels. When the Enter key is pressed, it results in the termination of the
server program. Thus, the destructor gets called. We are, however, not certain whether an object perishes or
survives when a program quits out. In any case, as far as the C# language is concerned, this is not in our control.

The Constructor gets called twice:


• On the first occasion, the framework reads the metadata .
• On the second occurrence, the client interacts with an instance of the object zzz.

The Destructor also gets called twice, since the framework brazenly destroys the two zzz objects which had been
created.

The client has to initially locate the server, and thereafter, connect to it, so that the zzz object gets instantiated.
The client requires a function to execute this task and to return an object, which is an instance of zzz. However,
the actual object dwells on another computer, which could be ensconced at a geographically dispersed location.
Thereafter, the function GetObject returns a simulation of the zzz object, which it instantiates. For all practical
purposes, it is under the delusion that it is the real zzz object, though in reality, the object has been indirectly
created on another machine.

Thus, GetObject returns a 'proxy' for the remote object. The literal meaning of the word 'proxy' is 'a surrogate of
the original'. This proxy then directs the call to the original object residing on an alternative machine. The return
value of the GetObject function is indicative of whether the object was successfully created on the remote
machine or not. A null value indicates an error. The function abc is called from the remote zzz object, after the
object has been successfully instantiated. However, a.abc() actually interacts with the proxy object, which in turn,
forwards the call to the remote machine. The remotely located program executes the function abc, and thereafter,
dispatches the return value over to the client.

Two other programs named 'proxy' and 'stub' are also brought into play, in order to accomplish the task of
'remoting'.

Let us now analyze this process from a fresh perspective.

At the outset, we need to create two sub-directories, r2 and r3.

c:\csharp>md r2
c:\csharp>md r3

Next, we are required to copy the server file s.cs and o.cs to the sub-directory r2, and the client file c.cs to the
sub-directory r3.

C:\csharp>copy s.cs r2
C:\csharp>copy o.cs r2
C:\csharp>copy c.cs r3

C:\csharp\r2> csc s.cs


C:\csharp\r2> csc /t:library o.cs

4
C:\csharp\r3> csc c.cs

c.cs(11,7): error CS0246: The type or namespace name 'zzz' could not be found (are you missing a using
directive or an assembly reference?)
c.cs(12,11): error CS0103: The name 'a' does not exist in the class or namespace 'ccc'
c.cs(15,19): error CS0246: The type or namespace name 'a' could not be found (are you missing a using
directive or an assembly reference?)

On compiling the two files placed in the sub-directory r2, no errors are generated. However, on compiling the
client program in the sub-directory r3, an error is generated.

We hit a roadblock at this juncture, since the file c.cs requires the assembly file in which, the remoted object,
o.dll resides. You may argue that the prerequisite of placing the dll in the client's sub-directory defeats the very
purpose of 'remoting'. However, the presence of the file is obligatory only for the metadata of the class zzz. The
motivation for doing this will be elucidated shortly.

C:\csharp\r3> copy c:\csharp\r2\o.dll


C:\csharp\r3> csc c.cs /r:o.dll

Therefore, we copy the assembly file o.dll from the sub-directory r2, into the sub-directory r3, and thereafter,
recompile it. Now, we add a tangy dash of lime to the mundane and bland routine given above, to give it a slight
twist. The string VijayMukhi2 is modified to VijayMukhi3. The library is recompiled to produce a dll file in the
sub-directory r2.

C:\csharp\r2>edit o.cs

public String abc()


{
Console.WriteLine("Vijay Mukhi3");
return "Sonal";
}

C:\csharp\r2>csc /t:library o.cs

As a consequence, the copies of the function abc, which reside in the two subdirectories r2 and r3, become
dissimilar.

Output in server dos box


Press <enter> to exit...
Constructor
Constructor
Vijay Mukhi3

Destructor
Destructor

Output in client dos box


Sonal

The server program and the client program are run in their respective directories. On doing so, it becomes evident
that, even though the client had a copy of the dll in its own sub-directory, it still called the function abc from
o.dll, which was resident in the sub-directory of the server.

Therefore, the output displayed is 'Vijay Mukhi3', and not 'Vijay Mukhi2'.

In real life, the code placed in the directories r2 and r3 would actually be dwelling within geographically dispersed
machines. The metadata of the object should be provided to the client, for the sole purpose of compilation.

5
o.cs
using System;
namespace nnn
{
public class zzz : MarshalByRefObject
{
public zzz()
{
Console.WriteLine("Constructor");
}
~zzz()
{
Console.WriteLine("Destructor");
}
public String abc()
{
Console.WriteLine("Vijay Mukhi2");
return "Sonal " ;
}
public String pqr(string s)
{
Console.WriteLine("Sonal Mukhi2");
return "VMCI " + s ;
}
}
}

s.cs
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
public class sss
{
public static void Main()
{
TcpChannel c = new TcpChannel(8085);
ChannelServices.RegisterChannel(c);
RemotingConfiguration.RegisterWellKnownServiceType(Type.GetType(
"nnn.zzz,o"), "eee", WellKnownObjectMode.SingleCall);
System.Console.WriteLine("Press <enter> to exit...");
System.Console.ReadLine();
}
}

c.cs
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using nnn;
public class ccc
{
public static void Main()
{
TcpChannel c = new TcpChannel();
ChannelServices.RegisterChannel(c);
zzz a = (zzz)Activator.GetObject(typeof(zzz), "tcp://localhost:8085/eee");
if (a == null)
System.Console.WriteLine("Could not locate server");
else

6
Console.WriteLine(a.abc());
Console.WriteLine(a.pqr("hell"));
}
}

Output Client
Sonal
VMCI hell

Output in server dos box


Press <enter> to exit...
Constructor
Constructor
Vijay Mukhi2
Constructor
Sonal Mukhi2

Destructor
Destructor
Destructor

We shall now initiate a few modifications in the three files, o.cs, s.cs and o.cs.

o.cs : Microsoft insists that all classes must reside in a namespace. Therefore, class zzz has been made part of the
nnn namespace. A function named pqr has been introduced, which accepts a string parameter and returns the
same string. The return value is prefixed with the acronym 'VMCI', which stands for my institute, i.e. Vijay
Mukhi's Computer Institute.

s.cs : The server has the name of the class nnn.zzz located within the function RegisterWellKnownServiceType. It
has no regard for the functions belonging to the class zzz, since its primary focus is on the endpoint that
represents the class.

c.cs : Internally within the client, the class has been renamed as nnn.zzz. Since we abhor the process of writing
the same ungainly names repeatedly, we use the keyword 'using' with nnn. As a result of this, every occurrence of
zzz is replaced by nnn.zzz. So, the typeof keyword encounters the class name nnn.zzz.

The function pqr is called with a single parameter. It is imperative to have the metadata available at this stage,
since it contains the following:
• The name of the class
• All the functions that the class carries
• The signatures of the functions.

This is a form of code validation, which ensures that inappropriate signatures are not used while the functions in
the client are being called.

The output generated by these two programs is very predictable. There is just a slight departure from the
expected output, in the case of the server window. Whenever a function from the remote server is to be executed,
the constructor in the class is called. However, the output of the destructors is highly unpredictable.

Therefore, it is possible for us to pass parameters to functions, even if the function is being remoted. Its
demeanor is very similar to that of calling the function off the same machine.

Interfaces

sh.cs
using System;
namespace nnn
{
public interface iii
{

7
String pqr(String s);
}
}

o.cs
using System;
namespace nnn
{
public class zzz : MarshalByRefObject, iii
{
public zzz()
{
Console.WriteLine("Constructor");
}
public String pqr(string s)
{
Console.WriteLine("Sonal Mukhi2");
return "VMCI " + s ;
}
}
}

s.cs
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
public class sss
{
public static void Main()
{
TcpChannel c = new TcpChannel(8085);
ChannelServices.RegisterChannel(c);
RemotingConfiguration.RegisterWellKnownServiceType(Type.GetType(
"nnn.zzz,o"), "eee", WellKnownObjectMode.SingleCall);
System.Console.WriteLine("Press <enter> to exit...");
System.Console.ReadLine();
}
}

c.cs
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using nnn;
public class ccc
{
public static void Main()
{
TcpChannel c = new TcpChannel();
ChannelServices.RegisterChannel(c);
iii a=(iii)Activator.GetObject(typeof(nnn.iii),"tcp://localhost:8085/eee");
if (a == null)
System.Console.WriteLine("Could not locate server");
else
{
Console.WriteLine(a.pqr("hell"));
}
}
}

8
a.bat
del *.exe
del *.dll
csc.exe /t:library sh.cs
csc.exe /t:library /r:sh.dll o.cs
csc.exe s.cs
csc.exe /r:sh.dll c.cs

Output Server
Press <enter> to exit...
Constructor
Constructor
Sonal Mukhi2

Output Client
VMCI hell

Interfaces are employed to overcome the requirement of placing the entire code of a class in an assembly, which is
resident on the client machine.

In the file sh.cs, a simple interface called iii is created in the namespace nnn, possessing a single function pqr. It
is imperative for all classes that derive from this interface, to incorporate this function. The interface iii is
compiled to a dll, and it does not require the /r: switch, since it does not refer to any external modules. The file
carries only the metadata, and does not contain any code.

In the object o.cs, class zzz is derived from both, the class MarshalByRefObject and the interface iii. Thus, it is
essential to implement the function pqr in the class zzz. While compiling the file o.cs, a reference must be
provided to the file sh.dll, which contains the metadata for the interface iii.

The server does not get affected in the least by any modifications carried out in the object. This is by virtue of the
framework, which registers a class zzz and not the interface iii. There is no necessity for the server to refer to any
of the assemblies of o.dll and sh.dll.

In the client program, the GetObject function instantiates an object of type iii, and not from the class zzz.
Whether the code is called from an interface or a class, is of no significance.

The prime benefit of using an interface is that the C# compiler does not need to introduce the assembly o.dll,
which contains a sizeable amount of code. The assembly file sh.dll is adequate, since it provides the metadata of
the interface. For example, Jack can create the interface iii on a separate machine and then, send across the file
sh.dll to his client Jil. She can, in turn, use it on her machine, thereby meeting the requirements of the complier.

The endpoint represents a zzz class, which in turn, represents an interface named iii, as well as, an object called
MarshalByRefObject. A request for an iii object in the client does not generate any error, since the endpoint
represents a zzz instance, which encompasses an iii instance.

We use the file a.bat to compile all the above four files with a single stroke. This program evinces how we can
separate the definition from the implementation.

For the next program, the file s.cs remains unchanged.

o.cs
using System;
namespace nnn
{
public class zzz : MarshalByRefObject
{
public zzz()
{
Console.WriteLine("Constructor");

9
}
public String pqr(string s)
{
Console.WriteLine("Sonal Mukhi2");
return "VMCI " + s ;
}
}
}

c.cs
using System;
using System.Threading;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
namespace nnn
{
public class ccc
{
public static ManualResetEvent e;
public delegate String ddd(String s);
public static void Main(string [] args)
{
e = new ManualResetEvent(false);
TcpChannel c = new TcpChannel();
ChannelServices.RegisterChannel(c);
zzz o = (zzz)Activator.GetObject(typeof(zzz), "tcp://localhost:8085/eee");
if (o == null)
System.Console.WriteLine("Could not locate server");
else
{
AsyncCallback cb = new AsyncCallback(ccc.abc);
ddd d = new ddd(o.pqr);
IAsyncResult ar = d.BeginInvoke("Vijay", cb, null);
Console.WriteLine(ar.IsCompleted);
}
e.WaitOne();
}
public static void abc(IAsyncResult ar)
{
ddd d = (ddd)((AsyncResult)ar).AsyncDelegate;
Console.WriteLine(d.EndInvoke(ar));
Console.WriteLine(ar.IsCompleted);
e.Set();
}
}
}

Output Client
False
VMCI Vijay
True

Output Server
Press <enter> to exit...
Constructor
Constructor
Sonal Mukhi2

In one of the earlier examples, we had called the pqr function from our client. At this point, a network message
was transmitted to the server, which executed the function abc and dispatched the outcome back to the client.

10
There is a likelihood that the function executed in the server, could have taken hours to complete execution. So,
the client would have had to wait eternally for the server. Therefore, this method is prodigiously wasteful in terms
of time and resources. Thus, whenever a 'synchronous call' is made to the remote object, the client has to cease
all activities, till it receives a response from the server.

It would prove to be a lot more efficacious if the client could be allowed to continue doing its job, while the server
is busy executing the function. On completion of the task, the server could notify the client. In other words, we do
not want the client to 'wait' or 'block'. This mechanism can be implemented by employing an 'asynchronous call'
to the function. In such cases, the call is made by the client. Therefore, only the code in the client needs
modification.

In the chapter on Threads, we had learnt about the ManualResetEvent event object. We had set it to a value of
False. In such situations, the client will wait on the WaitOne function, till the Set function is called.

A delegate is a type safe way of calling a method in an oblique manner. We have created a delegate ddd that
accepts a string as a parameter, and it returns a string. The framework can execute a function in an
asynchronous mode provided, we represent it as a delegate. Here, the callback function has been represented by
the use of a delegate. For those who have earned their spurs on C/C++, it would be a revelation that delegates are
actually 'pointers to functions'. The function pqr, which has been called in an asynchronous mode, accepts as
well as returns a string. Therefore, it is amply evident that the delegate uses the same parameters and return
types.

When we were writing a book on Intermediate Language, IL (an assembler language to which all code in the .NET
world gets converted), we had discovered that a delegate finally gets converted into a class. In our case, the
delegate ddd contains a large number of functions, which get added to it. Two such functions are, BeginInvoke
and EndInvoke. The main role of these functions is to call code that is written by the system or runtime. Hence,
they are called native functions.

Thereafter, an object cb, which is an instance of a delegate AsyncCallback, is created. The constructor of this
class is passed a function abc, which is notified as soon as the remote function completes execution. The callback
function abc is passed an IAsyncResult parameter, since this function matches the declaration of the delegate.

Next, we create a delegate d of type ddd, whose constructor is assigned the function name pqr, which is to be
executed in an asynchronous manner. The BeginInvoke function is called next. It is passed the parameter string,
which is to be transmitted to the remote function named pqr. The second parameter to this function is another
function called cb, which also requires to be notified after all the action has been wrapped up. This delegate
allusively represents the function abc in the class ccc.

The BeginInvoke function returns an IAsyncResult, which lies inert at this stage. The parameters passed to it are
similar to the ones provided to our event handling function abc.

The remote function may take excessive time to execute. Therefore, to avoid wasting time, the client continues
with its work. Once the call is completed, the framework ensures that the function abc is called with the
parameter representing the return value. To figure out the return value, we simply cast the parameter to a ddd
object, and call the function EndInvoke off it. This return value is displayed in the client dos box.

For Asynchronous calls, we create two delegates, one for the callback function, and the other for the remote
method. Function BeginInvoke calls the remote function, which in turn, calls the function abc. When the call
ceases, EndInvoke is called to receive the return values.

The IAsyncResult consists of five properties, one of which is IsCompleted. Initially, this property returns False,
since the asynchronous call has not been completed. However, in the function abc, it returns True, since the
remote call has run its course and is done with. The EndInvoke function plays no role in ending the remote call. If
we display the value contained in the IsCompleted property, prior to the EndInvoke function, a value of True
would get displayed.

sh.cs
using System;
namespace nnn

11
{
public class fff : MarshalByRefObject
{
public void xyz(String t)
{
Console.WriteLine(t);
}
}
}

>csc /t:library sh.cs

o.cs
using System;
using System.Runtime.Remoting;
namespace nnn
{
public class zzz : MarshalByRefObject
{
public zzz()
{
Console.WriteLine("Constructor");
}
public String pqr(String s,fff f)
{
f.xyz("Hi");
Console.WriteLine("pqr called");
return "VMCI " + s;
}
}
}

>csc /t:library o.cs /r:sh.dll

s.cs
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
public class sss
{
public static void Main()
{
TcpChannel c = new TcpChannel(8085);
ChannelServices.RegisterChannel(c);
RemotingConfiguration.RegisterWellKnownServiceType(Type.GetType(
"nnn.zzz,o"), "eee", WellKnownObjectMode.SingleCall);
System.Console.WriteLine("Press <enter> to exit...");
System.Console.ReadLine();
}
}

>csc s.cs

c.cs
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
namespace nnn
{
public class ccc

12
{
public static void Main(string [] args) {
TcpChannel c = new TcpChannel(8086);
ChannelServices.RegisterChannel(c);
fff f = new fff();
zzz o = (zzz)Activator.GetObject(typeof(nnn.zzz),"tcp://localhost:8085/eee");
if (o == null)
System.Console.WriteLine("Could not locate server");
else
Console.WriteLine(o.pqr("Vijay",f));
}
}
}

>csc /r:sh.dll /r:o.dll c.cs

Output in server dos box


Constructor
Constructor
Pqr called

Output in client dos box


Hi
VMCI Vijay
More often than not, the code for the server remains unchanged. In this program, our objective is to call a
function remotely, having a parameter which is of a 'user-defined' type. Therefore, in the file sh.cs, we have a
class called fff, which is derived from the class MarshalByRefObject. It contains a simple function called xyz,
which displays a string that is passed as a parameter. The program centers around passing an instance of a user-
defined class fff, as a parameter to the function pqr.

In the object file o.dll, the class zzz has a function pqr, which accepts two parameters viz. a string s, and an
object f. The object f is an instance of the class fff. Using the object f, the xyz function is called. This function is
passed the string 'hi'. The rest of the code remains unaltered. The server, as mentioned earlier, is not modified.

The client c.cs merely creates an object f, as an instance of class fff. This object is then passed as a parameter to
pqr, without paying any cognizance to the fact that, the data shall be sent to another computer located in some
other part of the world. The client couldn't care less, since this class is derived from the class
MarshalByRefObject. This is the only prerequisite imposed on it, when instances of user-defined type are used as
parameters.

While compiling, references are to be given to the files o.dll and sh.dll, since both contain the metadata
information required for error checking. The output in the client box displays 'Hi'.

You should note that a port number has been furnished in the client program. Earlier, the port number had been
provided only in the server program. By having two ports registered, the framework can use the two port numbers
for different purposes. While one port number may be used to enable the Client to pass parameters to the server,
the other may be used to permit the server to pass parameters to the client. This facilitates bi-directional
communication between servers and clients, thereby, permitting the use of remote parameters.

Passing by value

sh.cs
using System;
namespace nnn
{
[Serializable]
public class fff

13
{
int j = 1;
public fff()
{
Console.WriteLine("Constructor " + j);
}
public void xyz()
{
j++;
}
public int aaa()
{
return j;
}
}
}

o.cs
using System;
using System.Runtime.Remoting;
namespace nnn
{
public class zzz : MarshalByRefObject
{
public zzz()
{
Console.WriteLine("Constructor");
}
public fff pqr(fff o)
{
o.xyz();
o.xyz();
return o;
}
}
}

c.cs
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
namespace nnn
{
public class ccc
{
public static void Main(string [] args)
{
TcpChannel c = new TcpChannel(8086);
ChannelServices.RegisterChannel(c);
fff p = new fff();
zzz o = (zzz)Activator.GetObject(typeof(nnn.zzz),"Tcp://localhost:8085/eee");
if (o == null)
System.Console.WriteLine("Could not locate server");
else
{
Console.WriteLine("Before " + p.aaa());
fff a = o.pqr(p);
Console.WriteLine("After " + a.aaa() + " " + p.aaa());
}
}
}

14
}

Server Output
Constructor
Constructor

Client Output
Constructor 1
Before 1
After 3 1

In the file sh.cs, we have two functions, aaa and xyz, and a single variable j. Their roles are as follows:
• The constructor displays the value of variable j.
• The function xyz increments its value by one.
• The function aaa returns the value of the variable.

The variable j could have been accessed directly, by making it public. Instead, the program uses functions to
access it. The fff class, for some reason, has to be made Serializable.

In the file o.cs, the object zzz contains a function pqr. This function accepts an fff instance as a parameter, and
then returns a object of the same type. The xyz function in fff is called twice. An object that is an instance of a
particular class, is different from another object, which may be an instance of the same class. There is no
difference in the functions contained in the objects, since the code is identical amongst the instances. The
variation occurs by virtue of the values of the instance variables, contained in these objects. Thus, the variable j
will possess dissimilar values in the varied instances of the same class.

In the client, a new fff instance named 'p' is created. The constructor is called, which results in display of the text
"Constructor 1", since the current value of the object j is 1. The value in j is again printed, by calling the function
aaa.

Thereafter, function pqr is called with the parameter p, which is an fff object. The value of the variable j, which is
contained in p, is presently 1. The function pqr calls the function xyz from the class fff twice. As a consequence of
this, the value of j increases by 2. The fff instance, which is returned back by this function, is stored in 'a'. The
WriteLine function then prints the value of j from both the fff objects, p and a.

The object p is local to the client. Therefore, p.aaa would always display the value 1. The object 'a' that has been
'remoted', prints the value of j as 3.

The names of objects are of no consequence at all, across computers. They are not visible in the IL code generated
either. While remoting, the values in the local objects get handed over to a remote function. The modified value of
the variable is displayed only in the new instance of the object. The local instance continues to display the
original value.

Singleton

o.cs
using System;
namespace nnn
{
public class zzz : MarshalByRefObject
{
public zzz()
{
Console.WriteLine("Constructor");
}
~zzz()
{
Console.WriteLine("Destructor");
}
public String pqr()

15
{
Console.WriteLine("pqr");
return "VMCI ";
}
}
}

s.cs
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
public class sss
{
public static void Main()
{
TcpChannel c = new TcpChannel(8085);
ChannelServices.RegisterChannel(c);
RemotingConfiguration.RegisterWellKnownServiceType(Type.GetType("
nnn.zzz,o"), "eee", WellKnownObjectMode.Singleton);
System.Console.WriteLine("Press <enter> to exit...");
System.Console.ReadLine();
}
}

c.cs
using System;
using System.Threading;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
namespace nnn
{
public class ccc
{
public static void Main(string [] args)
{
TcpChannel c1 = new TcpChannel();
ChannelServices.RegisterChannel(c1);
zzz o = (zzz)Activator.GetObject(typeof(zzz),"tcp://localhost:8085/eee");
Console.WriteLine(o.pqr());
Console.WriteLine(o.pqr());
}
}
}

Server Output
Press <enter> to exit...
Constructor
pqr
pqr

Destructor

Client Output
VMCI
VMCI

The object zzz, which is to be remoted, resides in file o.cs and has a single function pqr, which returns 'VMCI'. It
also contains a constructor and a destructor. However, its contents are nothing earth shaking.
We have only carried out a single modification in the server program. The last parameter to the function
RegisterWellKnownServiceType is changed to Singleton. This is suggestive of the fact that, all requests to the

16
remoting object are to be handled by the same instance of zzz. It does not create a separate instance of the object,
every time the framework receives a request. As the name itself suggests, Singleton means 'single' or 'only one
entity'.

The client calls the function pqr twice. However, the constructor is called only once at the beginning, in order to
read the metadata. The object is not created repeatedly on each request. Therefore, neither the constructor, nor
the destructor is called.

s.cs
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Channels.Http;

namespace nnn {
public class sss {

public static void Main() {


TcpChannel c = new TcpChannel(8085);
ChannelServices.RegisterChannel(c);
HttpChannel c1 = new HttpChannel(8086);
ChannelServices.RegisterChannel(c1);
RemotingConfiguration.RegisterWellKnownServiceType(Type.GetType("
nnn.zzz,o"),"eee", WellKnownObjectMode.Singleton);
System.Console.WriteLine("Press <enter> to exit...");
System.Console.ReadLine();
}
}
}

c.cs
using System;
using System.Threading;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Remoting.Channels.Tcp;
namespace nnn
{
public class ccc
{
public static void Main(string [] args)
{
HttpChannel c = new HttpChannel();
ChannelServices.RegisterChannel(c);
TcpChannel c1 = new TcpChannel();
ChannelServices.RegisterChannel(c1);
zzz o = (zzz)Activator.GetObject(typeof(zzz),"http://localhost:8086/eee");
zzz o1 = (zzz)Activator.GetObject(typeof(zzz),"tcp://localhost:8085/eee");
Console.WriteLine(o1.pqr());
Console.WriteLine(o1.pqr());
Console.WriteLine(o.pqr());
Console.WriteLine(o.pqr());
}
}
}

Server Output
Press <enter> to exit...
Constructor
pqr

17
pqr
pqr
pqr

Destructor

Client Output
VMCI
VMCI
VMCI
VMCI

The server has created two channels; one is the normal channel named TcpChannel, while the other one is called
an HttpChannel. A separate port number has to be allotted to each of these channels. Therefore, the number
8085 is assigned to the TCP channel, while the number 8086 is assigned to the HTTP channel. The registration
process does not delve into mundane issues, such as, the type of channel that has been used.

The client creates both, an HttpChannel object, as well as a TcpChannel object. However, it does not specify the
port numbers to the constructors. The GetObject function is used to specify a proxy. A major modification that
has been incorporated here is, the HttpChannel being prefixed with http:, and the TcpChannel being prefixed
with tcp:. The endpoint URI is retained as eee. The http channel connects to the server on port 8086, while the
tcp channel connects to the server on port 8085. If you interchange the port numbers, the program will pause
endlessly.

The pqr function is called, by utilizing the http channel and the tcp channel. Since the call is made 4 times, 'pqr'
is also displayed 4 times on our screen.

We have not used a stopwatch to time the output generation; however, we were able to conclude that, the HTTP
(Hyper Text Transfer Protocol), is much slower than the TCP (Transmission Control Protocol). This difference in
the speeds of HTTP and TCP occurs because HTTP uses both SOAP (the Simple Object Access Protocol) and XML,
which slows it down, whereas, TCP uses a binary protocol, which makes it relatively faster.

A Singleton object preserves its state between function invocations, since it is not created each time that the
function is invoked. A port number is not specified while registering a client channel, since the framework assigns
it internally. This is dependent upon the task to be performed, such as, listening or connecting. The framework
connects to a remote channel, using the URI specified. If we actually specify a port number in the client, it starts
behaving like a server, i.e. it too listens on the specified port.

18

Vous aimerez peut-être aussi