Multiserver Configuration

When using multiple servers in an application in which remote objects on one server will be passed as parameters to methods of a second server's object, there are a few things you need to consider.

Before talking about cross-server execution, I show you some details of remoting with MarshalByRefObjects. As the name implies, these objects are marshaled by reference—instead of passing a copy of the object over the network, only a pointer to this object, known as an ObjRef, will travel. Contrary to common pointers in languages like C++, ObjRefs don't reference a memory address but instead contain a network address (like a TCP/IP address and TCP port) and an object ID that's employed on the server to identify which object instance is used by the calling client. (You can read more on ObjRefs in Chapter 7.) On the client side these ObjRefs are encapsulated by a proxy object (actually, by two proxies, but you also get the chance to read more on those in Chapter 7).

After creating two references to client-activated objects on a remote server, for example, the client will hold two TransparentProxy objects. These objects will both contain an ObjRef object, which will in turn point to one of the two distinct CAOs. This is shown in Figure 3-23.

click to expand
Figure 3-23: ObjRefs are pointing to server-side objects.

When a variable referencing a MarshalByRefObject is passed as a parameter to a remote function, the following happens: the ObjRef is taken from the proxy object, gets serialized (ObjRef is [Serializable]), and is passed to the remote machine (the second server in this example). On this machine, new proxy objects are generated from the deserialized ObjRef. Any calls from the second machine to the remote object are placed directly on the first server without any intermediate steps via the client.


As the second server will contact the first one directly, there has to be a means of communication between them; that is, if there is a firewall separating the two machines, you have to configure it to allow connections from one server to the other.

Examining a Sample Multiserver Application

In the following example, I show you how to create a multiserver application in which Server 1 will provide a Singleton object that has an instance variable of type int. The client will obtain a remote reference to this object and pass it to a "worker object" located on a secondary server. This worker object is a SingleCall service providing a doSomething() method, which takes an instance of the first object as a parameter. Figure 3-24 shows the Unified Modeling Language (UML) diagram for this setup.

click to expand
Figure 3-24: UML diagram of the multiserver example


For this example, I change the approach from using interfaces in General.dll to using abstract base classes. The reason for the change is that, upon passing a MarshalByRefObject to another server, the ObjRef is serialized and deserialized. On the server side, during the deserialization, the .NET Remoting Framework will generate a new proxy object and afterwards will try to downcast it to the correct type (cast from MarshalByRefObject to BaseRemoteObject in this example). This is possible because the ObjRef includes information about the type and its class hierarchy. Unfortunately, the .NET Remoting Framework does not also serialize the interface hierarchy in the ObjRef, so these interface casts would not succeed.

Figures 3-25 to 3-27 illustrate the data flow between the various components. In Figure 3-25, you see the situation after the first method call of the client on the first server object. The client holds a proxy object containing the ObjRef that points to the server-side Singleton object.

click to expand
Figure 3-25: Client and single server

click to expand
Figure 3-26: Client calls a method on the second server with MRO#1 as parameter.

click to expand
Figure 3-27: Calls to the first server will go there directly without passing the client


I use IDs like MRO#1 for an instance of MyRemoteObject not because that's .NET-like, but because it allows me to more easily refer to a certain object instance when describing the architecture.

In the next step, which you can see in Figure 3-26, the client obtains a reference to the MarshalByRefObject called MyWorkerObject on the second server. It calls a method and passes its reference to the first server's object as a parameter. The ObjRef to this object (MRO#1) is serialized at the client and deserialized at the server, and a new proxy object is generated that sits on the second server and points to the object on the first. (Figure 3-27.) When MWO#1 now calls a method on MRO#1, the call will go directly from Server 2 to Server 1.

Implimenting the Shared Assembly

In the shared assembly, which is shown in Listing 3-19, you have to change the approch from using interfaces (which have been used in the prior examples) to abstract base classes because of the reasons stated previously. These are the superclasses of the classes you will implement in the two servers, therefore they have to descend from MarshalByRefObject as well.

Listing 3-19: Using Abstract Base Classes in the Shared Assembly

start example
 using System; Namespace General {    public abstract class BaseRemoteObject: MarshalByRefObject    {       public abstract void setValue(int newval);       public abstract int getValue();    }    public abstract class BaseWorkerObject MarshalByRefObject    {       public abstract void doSomething(BaseRemoteObject usethis);    } } 
end example

The BaseRemoteObject's descendant is a Singleton located on the first server, and it allows the client to set and read an int as state information. The BaseWorkerObject's implementation is placed in Server 2 and provides a method that takes an object of type BaseRemoteObject as a parameter.

Implementing the First Server

The first server very closely resembles the servers from the other examples. The only difference is that MyRemoteObject is no direct child of MarshalByRefObject, but instead is a descendant of BaseRemoteObject, defined in the shared assembly.

This object, implemented as a Singleton, is shown in Listing 3-20.

Listing 3-20: The First Server

start example
 using System; using System.Runtime.Remoting; using General; using System.Runtime.Remoting.Channels.Http; using System.Runtime.Remoting.Channels; namespace Server {    class MyRemoteObject: BaseRemoteObject    {       int myvalue;       public MyRemoteObject()       {          Console.WriteLine("MyRemoteObject.Constructor: New Object created");       }       public override void setValue(int newval)       {          Console.WriteLine("MyRemoteObject.setValue(): old {0} new {1}",                                myvalue,newval);          myvalue = newval;       }       public override int getValue()       {          Console.WriteLine("MyRemoteObject.getValue(): current {0}",myvalue);          return myvalue;       }    }    class ServerStartup    {       static void Main(string[] args)       {          Console.WriteLine ("ServerStartup.Main(): Server [1] started");          HttpChannel chnl = new HttpChannel(1234);          ChannelServices.RegisterChannel(chnl);          RemotingConfiguration.RegisterWellKnownServiceType(                typeof(MyRemoteObject),                "MyRemoteObject.soap",                WellKnownObjectMode.Singleton);          // the server will keep running until keypress.          Console.ReadLine();       }    } } 
end example

Implementing the Second Server

The second server works differently from those in prior examples. It provides a SingleCall object that accepts a BaseRemoteObject as a parameter. The SAO will contact this remote object, read and output its state, and change it before returning.

The server's startup code is quite straightforward and works the same as in the preceding examples. It opens an HTTP channel on port 1235 and registers the well-known object. This second server is shown in Listing 3-21.

Listing 3-21: The Second Server

start example
 using System; using System.Runtime.Remoting; using General; using System.Runtime.Remoting.Channels.Http; using System.Runtime.Remoting.Channels; using System.Collections; namespace Server {    class MyWorkerObject: BaseWorkerObject    {       public MyWorkerObject()       {          Console.WriteLine("MyWorkerObject.Constructor: New Object created");       }       public override void doSomething(BaseRemoteObject usethis)       {          Console.WriteLine("MyWorkerObject.doSomething(): called");          Console.WriteLine("MyWorkerObject.doSomething(): Will now call" +                         "getValue() on the remote obj.");          int tmp = usethis.getValue();          Console.WriteLine("MyWorkerObject.doSomething(): current value of " +                          "the remote obj.; {0}", tmp);          Console.WriteLine("MyWorkerObject.doSomething(): changing value to 70");          usethis.setValue(70);       }    }    class ServerStartup    {       static void Main(string[] args)       {          Console.WriteLine ("ServerStartup.Main(): Server [2] started");          HttpChannel chnl = new HttpChannel(1235);          ChannelServices.RegisterChannel(chnl);          RemotingConfiguration.RegisterWellKnownServiceType(                typeof(MyWorkerObject),                "MyWorkerObject.soap",                WellKnownObjectMode.SingleCall);          // the server will keep running until keypress.          Console.ReadLine();       }    } } 
end example


When running two servers on one machine, you have to give the servers different port numbers. Only one application can occupy a certain port at any given time.When developing production-quality applications, you should always allow the user or system administrator to configure the port numbers in a configuration file, via the registry or using a GUI.

Running the Sample

When the client is started, it first acquires a remote reference to MyRemoteObject running on the first server. It then changes the object's state to contain the value 42 and afterwards reads the value from the server and outputs it in the console window (see Figure 3-28).

click to expand
Figure 3-28: The client's output

Next it fetches a remote reference to MyWorkerObject running on the second server. The client calls the method doSomething() and passes its reference to MyRemoteObject as a parameter. When Server 2 receives this call, it contacts Server 1 to read the current value from MyRemoteObject and afterwards changes it to 70. (See Figures 3-29 and 3-30.)

click to expand
Figure 3-29: The first server's output

click to expand
Figure 3-30: The second server's output

When the call from client to the second server returns, the client again contacts MyRemoteObject to obtain the current value, 70, which shows that your client really has been talking to the same object from both processes.

Advanced  .NET Remoting C# Edition
Advanced .NET Remoting (C# Edition)
ISBN: 1590590252
EAN: 2147483647
Year: 2002
Pages: 91
Authors: Ingo Rammer © 2008-2017.
If you may any questions please contact us: