DekGenius.com
[ Team LiB ] Previous Section Next Section

4.2 Distributed Components

A component technology should support distributed computing, allowing you to activate and invoke remote services, as well as services in another application domain.[5] Distributed COM, or DCOM, is the wire protocol that provides support for distributed computing using COM. Although DCOM is fine for distributed computing, it is inappropriate for global cyberspace because it doesn't work well in the face of firewalls and NAT software. Some other shortcomings of DCOM are expensive lifecycle management, protocol negotiation, and binary formats.

[5] Each Windows process requires its own memory address space, making it fairly expensive to run multiple Windows processes. An application domain is a lightweight or virtual process. All application domains of a given Windows process can use the same memory address space.

To eliminate or at least mitigate these shortcomings, .NET provides a host of different distributed support. The Remoting API in .NET allows you to use a host of channels, such as TCP and HTTP (which uses SOAP by default), for distributed computing. It even permits you to plug in your own custom channels, should you require this functionality. Best of all, since the framework is totally object-oriented, distributed computing in .NET couldn't be easier. To show you how simple it is to write a distributed application in .NET, let's look at an example using sockets, otherwise known as the TCP channel in .NET.

4.2.1 Distributed Hello Server

In this example, we'll write a distributed Hello application, which outputs a line of text to the console whenever a client invokes its exposed method, SayHello( ). Since we're using the TCP channel, we'll tell the compiler that we need the definitions in the System.Runtime.Remoting and System.Runtime.Remoting.Channels.Tcp namespaces.

Note that this class, CoHello, derives from MarshalByRefObject.[6]

[6] If you fail to do this, your object will not have a distributed identity since the default is marshal-by-value, which means that a copy of the remote object is created on the client side.

This is the key to distributed computing in .NET because it gives this object a distributed identity, allowing the object to be referenced across application domains, or even process and machine boundaries. A marshal-by-reference object requires a proxy to be set up on the client side and a stub to be set up on the server side, but since both of these are automatically provided by the infrastructure, you don't have to do any extra work. Your job is to derive from MarshalByRefObject to get all the support for distributed computing:

using System;

using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;

public class CoHello : MarshalByRefObject 
{
  public static void Main(  ) 
  {
    TcpChannel channel = new TcpChannel(4000);
    ChannelServices.RegisterChannel(channel);

    RemotingConfiguration.RegisterWellKnownServiceType (
      typeof(CoHello),              // Type name
      "HelloDotNet",                // URI
      WellKnownObjectMode.Singleton // SingleCall or Singleton
    );

    System.Console.WriteLine("Hit <enter> to exit . . . ");
    System.Console.ReadLine(  );
  }

  public void SayHello(  )
  {
    Console.WriteLine("Hello, Universe of .NET");
  }
}

The SayHello( ) method is public, meaning that any external client can call this method. As you can see, this method is very simple, but the interesting thing is that a remote client application (which we'll develop shortly) can call it because the Main( ) function uses the TcpChannel class. Look carefully at Main( ), and you'll see that it instantiates a TcpChannel, passing in a port number from which the server will listen for incoming requests.[7]

[7] Believe it or not, all you really have to do is replace TcpChannel with HttpChannel to take advantage of HTTP and SOAP as the underlying communication protocols.

Once we have created a channel object, we then register the channel to the ChannelServices, which supports channel registration and object resolution. Having done this, you must then register your object with the RemotingConfiguration so that it can be activated—you do this by calling the RegisterWellKnownServiceType( ) method of the RemotingConfiguration class. When you call this method, you must pass in the class name, a URI, and an object-activation mode. The URI is important because it's a key element that the client application will use to refer specifically to this registered object. The object-activation mode can be either Singleton, which means that the same object will service many calls, or SingleCall, which means an object will service at most one call.

Here's how to build this distributed application:

csc server.cs

Once you've done this, you can start the server program, which will wait endlessly until you hit the Enter key. The server is now ready to service client requests.

4.2.2 Remote Hello Client

Now that we have a server waiting, let's develop a client to invoke the remote SayHello( ) method. Instead of registering an object with the remoting configuration, we need to activate a remote object. So let's jump into the code now to see how this works. As you examine the following program, note these items:

  • We're using types in the System.Runtime.Remoting and System.Runtime.Remoting.Channels.Tcp namespaces, since we want to use the TCP channel.

  • Our Client class doesn't need to derive from anything because it's not a server-side object that needs to have a distributed identity.

  • Since we're developing a client application, we don't need to specify a client port when we instantiate the TcpChannel.

Other than these items, the key thing to note is object activation, shown in the second boldface statement in the following code. To invoke remote methods, you must first activate the remote object and obtain an associated proxy on the client side. To activate the object and get a reference to the associated proxy, you call the GetObject( ) method of the Activator class. When you do this, you must pass along the remote class name and its fully qualified location, including the complete URI. Once you've successfully done this, you can then invoke remote methods.

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;

public class Client 
{
  public static void Main(  ) 
  {
    try
    {
      TcpChannel channel = new TcpChannel(  );
      ChannelServices.RegisterChannel(channel);

      CoHello h = (CoHello) Activator.GetObject(
        typeof(CoHello),                    // Remote type
        "tcp://127.0.0.1:4000/HelloDotNet"  // Location
      );

      h.SayHello(  ); 
    }
    catch(Exception e)
    {
      Console.WriteLine(e.ToString(  ));
    }
  }
}

To build this client application, you must include references to the server.exe assembly:

csc /r:Server.exe Client.cs

If you're familiar with DCOM, you must be relieved to find that it's relatively simple to write distributed applications in .NET.[8]

[8] In fact, if you have a copy of Learning DCOM (O'Reilly) handy, compare these programs with their DCOM counterparts in Appendix D, and you will see what we mean.

4.2.3 Distributed Garbage Collector

Because the .NET distributed garbage collector is different from that of DCOM, we must briefly cover this facility. Instead of using DCOM's delta pinging, which requires few network packets when compared to normal pinging (but still too many for a distributed protocol), .NET remoting uses leases to manage object lifetimes. If you've ever renewed the lease to an IP address on your Dynamic Host Configuration Protocol (DHCP) network, you've pretty much figured out this mechanism because it's based on similar concepts.

In .NET, distributed objects give out leases instead of relying on reference counting (as in COM) for lifetime management. An application domain where the remote objects reside has a special object called the lease manager, which manages all the leases associated with these remote objects. When a lease expires, the lease manager contacts a sponsor, telling the sponsor that the lease has expired. A sponsor is simply a client that has previously registered itself with the lease manager during an activation call, indicating to the lease manager that it wants to know when a lease expires. If the lease manager can contact the sponsor, the sponsor may then renew the lease. If the sponsor refuses to renew the lease or if the lease manager can't contact the sponsor after a configurable timeout period, the lease manager will void the lease and remove the object. There are two other ways in which a lease can be renewed: implicitly, via each call to the remote object, or explicitly, by calling the Renew( ) method of the ILease interface.

    [ Team LiB ] Previous Section Next Section