Team LiB
Previous Section Next Section

Remoting Architecture

Distributed applications enable you to leverage the power of additional machines to serve as the workhorses of your application. In the following sections, you will learn how to create distributed applications using .NET Remoting.

Introduction to Remoting

.NET Remoting, as shown in Figure 39.1, is a technology that enables you to easily distribute application components across processes and computers. It allows you to seamlessly integrate these distributed components into your application. To better understand .NET Remoting, it is important to understand its architecture and terminology.

Figure 39.1. The .NET Remoting model.


When you look at an unmanaged application and how the operating system handles it, you can see that the unmanaged application is separated from other applications by a boundary around its code. The isolated code is known as a process. Without the use of some sort of interprocess communication (IPC) method, it is not possible for one application (process) to communicate with another. This can sometimes be a pain, but it comes with some benefits. The benefit of not being able to communicate with another process is that if you can't communicate with it, it is unlikely that your application will break it. In other words, if an application does manage itself well (overwriting memory and so forth), you probably don't want it interfering with your application and will be glad that it can't.

With .NET comes the concept of running multiple applications within the same process. This is possible because .NET ensures two things: type safety and that applications can't access invalid memory locations. In the old model (unmanaged code), you had a process. With the new .NET model, you still have a process, but .NET adds two more logical subdivisions: application domain and context.

Explaining Application Domains

At a basic level, operating systems host processes. In the unmanaged world, processes form a boundary around a single application. A process can be loaded and unloaded (terminated). However, with the loading and unloading of a process comes a lot of overhead. To overcome this, .NET takes a different approach. In .NET, applications and assemblies are hosted inside of an application domain (also known as an AppDomain). An AppDomain forms a boundary around your managed code, as shown in Figure 39.2.

Figure 39.2. An application domain.


A process can host one or more application domains. However, simply because a process can host more then one application domain does not eliminate the need for isolation. User code from one application domain cannot directly access code from another application domain, nor can data be shared directly.

TIP

An AppDomain provides isolation, unloading, and security boundaries for the managed application. When the Common Language Runtime is first loaded into a process, it creates a default application domain to execute an application's code. From there, both the Common Language Runtime and user code can create additional AppDomains.

Application domains are isolated from each other. In other words, information cannot be directly shared across AppDomains, nor can one AppDomain directly access another. The very fact that application domains are isolated from each other gives you the ability to unload an application domain. In fact, as it turns out, AppDomains are the smallest unit that can be unloaded from a process. Although you can load assemblies, you can not directly unload an assembly.


AppDomains and Domain-Neutral Assemblies

By default, the Common Language Runtime loads an assembly into an AppDomain that contains the code that references that assembly. This allows the code and assembly to be isolated. However, if an assembly is used by multiple domains in the same process, the code (not the data) can be shared among all the domains that reference that assembly. If an assembly can be used by all domains in the process, an assembly is said to be domain neutral. There are the options for loading domain-neutral assemblies:

  • Don't load any assemblies as domain neutral

  • Load all assemblies as domain neutral

  • Load only strongly named assemblies as domain neutral

Each option gives you a different benefit and should be used in particular circumstances. Use the first option when there is only one application in the process. Use the second option if you have multiple domains, in the same process, which use the same code. Finally, use the third option when you are running multiple applications in the same process.

Global Exception Handler

When an unhandled exception is encountered, an application (managed or unmanaged) terminates. Because programs are written by programmers who are sometime forced to take shortcuts, this is often a little annoying (especially when you are trying to give a demo that you spent all night trying to produce and your manager presses a key that he knew he shouldn't touch). Fortunately, .NET enables you to associate a global exception handler with an application domain. A global exception handler enables you to trap and cover up any unforeseen code "opportunities." Listing 39.1 shows an example of a global exception handler.

Listing 39.1. Global Exception Handler
// Project AppDomain/ExceptionHandler
using System;

namespace SAMS.VisualCSharpDotNetUnleashed.Chapter39
{
  class GEHApplication
  {
    static void GlobalExceptionHandler(Object sender,
      UnhandledExceptionEventArgs e)
    {
      Console.WriteLine(e.ExceptionObject.ToString());
    }

    public static void Main()
    {
      // Add a global exception handler to the current Application Domain
      AppDomain appDomain = AppDomain.CurrentDomain;
      appDomain.UnhandledException +=
        new  UnhandledExceptionEventHandler(GlobalExceptionHandler);

      // ***** From This point you should be protected! *****

      // throw an exception
      throw new Exception("It worked before you touched it!");
    }
  }
}

Understanding the Context

In .NET, a class can be configured to use different services such as synchronization, transactions, just-in-time (JIT) activation, and security. The way in which these services are configured for an object is called a context. Each time a class is instantiated, the runtime places it into a context. The runtime first searches for a compatible context. If a compatible context is not found, the runtime creates a new one. However, after an instantiated class has been placed into a context, it remains there for the rest of its life (which is over when the object is collected by the Garbage Collector).

A context resides in a application domain, as shown in Figure 39.3. An AppDomain can have one or more contexts. When an application domain is created, it creates a default context with a Context.ContextID of 0. Each subsequent Context that is created receives the next ContextID. It is important to note, however, that the ContextID is unique only within an application domain.

Figure 39.3. The context.


Choosing a Channel

A channel is a transport mechanism. It transports messages between Remoting boundaries such as application domains, process, and computers. Built into the .NET Framework are two channels: HttpChannel and TcpChannel. In addition, the .NET Framework enables you to create custom channels, although doing so is beyond the scope of this book.

The HttpChannel and TcpChannel channels are similar. However, they do have some differences. TcpChannel uses the binary formatter to serialize messages, whereas HttpChannel uses the SOAP-XML formatter. Now that you have a basic understanding of channels, it will be helpful for you to understand the common terms described in Table 39.1.

Table 39.1. Common Channel Terms

Term

Description

Object URI

This represents a well-known object that is registered on the server.

Channel URI

This string represents the connection information to the server. The Channel URI is specified in the form: http://hostname:port

Server-Activated URL

This is a unique string that is used by the client to connect to the server object. It is in the format:

Channel-uri/object-url

For example: http://hostname:port/object-url

Client-Activated URL

This string is similar to the server-activated URL except that it does not have to be unique.


To choose between using an HttpChannel and a TcpChannel, it is useful to know some of the differences between the two. Table 39.2 describes some differences, benefits, and limitations.

Table 39.2. Choosing Between the Two Built-in Channels

Channel

Comments

Efficiency

Security

HttpChannel

This channel enables you to host objects on an HTTP server. This adds the ability to utilize some of the features that come with a web server, such as web farms and so on.

Because the HttpChannel uses HTTP as its transport, it has more overhead than the TcpChannel.

Because the HttpChannel is hosted by a web server (such as IIS), it can immediately take advantage of the Secure Sockets Layer (SSL).

TcpChannel

This channel enables you to host objects on a particular TCP port. Unfortunately, in today's world, it is very hard to get an IS department to expose a port to the Internet.

TcpChannel uses raw sockets to transmit the data and therefore has less overhead than the HttpChannel.

A TCP port is less secure then a web server. However, you can implement a custom security system using the class in the System.Security namespace.


Life and Death of the Remote Object

Every instance of a class has a beginning and an end to its life. The questions with remote objects involve when they are instantiated and when they are terminated. The answers depend upon the mode of the instantiation or, in the case of .NET Remoting, activation. .NET offers three ways of activating a remote object: single call, singleton, and client activated.

Single Call Remote Objects

With single call activation, the remote object is created with each call to it and is destroyed at the end of the call. Because the object is created and destroyed with each call, this object typically does not maintain state. Although it is possible to maintain state (you would have to maintain the state yourself), it would simply be too much overhead to make this practical. This type of activation is useful for stateless programming, such as web applications. Figure 39.4 shows the lifetime of the single call remote object.

Figure 39.4. Single call remote object lifetime.


Singleton Remote Objects

Singleton activation is a little different from the other two methods. The remote object is activated upon the first call to the remote object, using the Singleton method. Subsequent calls to that remote object, regardless of which client is calling the remote object, use the same instance of the remote object. The remote object terminates only when its lifetime expires. As shown in Figure 39.5, each client uses the same instance of the remote object. This can make the chore of maintaining state fairly simple. However, because all clients share the same instance, you must take care to differentiate between the client calls.

Figure 39.5. Singleton remote object lifetime.


Client-Activated Remote Objects

With client-activated remote objects, the server activates the object when the client requests its activation. This is different from the other two methods in that with the other methods, the server determines when the remote object is activated and when it is destroyed. As with the single call method, each client gets its own instance of the remote object. However, because the object is not terminated with each call, you have the option of easily maintaining state.

Terminating the remote object with the client-activated method is also a little different. In both the singleton and the single call methods, the server determines when to destroy the object. With the client-activated method, the object is either destroyed upon request or at the termination of its lease. The lease is actually a contract between the client and server. It specifies how long the remote object can live before it is marked for garbage collection. As it is in real life, when the lease is about to expire, the server contacts any of the object's sponsors and asks whether they would like to extend the lease. If not, the object is marked for garbage collection. In addition, you can set the lease to reset upon each call to the remote object. Figure 39.6 shows the activation process and lifetime of a remote object that is activated using the client activation method.

Figure 39.6. Client-activated remote object lifetime.


Registering the Remote Object

In the single call and singleton methods, you must register a remote object type before you can expose the object to other AppDomains. There are two ways to do this: by registering the types programmatically and by using a configuration file.

Registering Programmatically

The first step in exposing the Remoting object is to register the channel. As described earlier in this chapter, this can be accomplished by calling the ChannelServices.RegisterChannel method, as in Listing 39.2.

Listing 39.2. Programmatically Registering the Single Call and Singleton Methods of Activation on the Server
   // Project Remoting Sample
   // This code listing does not contain the sources for
   // the sample remote object.  It is only meant to show
   // how to expose the object to other application domains.

   using System;

   namespace SAMS.VisualCSharpDotNetUnleashed.Chapter39
   {
     class RemotingSample
     {
       public static void Main()
       {
         HttpChannel httpChannel = new HttpChannel(9000);
         ChannelServices.RegisterChannel(httpChannel);
         RemotingConfiguration.RegisterWellKnownServiceType(typeof(SampleService),
             "SAMSSampleService/SampleService.soap", WellKnownObjectMode.Singleton);
         Console.WriteLine("** Press enter to end the server process. **");
         Console.ReadLine();
       }
     }
   }

To expose a client-activated object, replace the shaded area in Listing 39.2 with the following code snippet:

RemotingConfiguration.RegisterActivatedServiceType(typeof(SampleService));

The code in Listing 39.3 illustrates programmatically registering single-call and singleton methods on the client.

Listing 39.3. Programmatically Registering the Single Call and Singleton Method of Activation on the Client
// Project Remoting Sample
// This code listing does not contain the sources for
// the sample remote object.  It is only meant to show
// how to expose the object to other application domains.

using System;

namespace SAMS.VisualCSharpDotNetUnleashed.Chapter39
{
  class RemotingSample
  {
    public static void Main()
    {
      HttpChannel httpChannel = new HttpChannel(8085);
      ChannelServices.RegisterChannel(httpChannel);

      RemotingConfiguration.RegisterWellKnownClientType(typeof(SampleService),
          "tcp://localhost:9000/SAMSSampleService/SampleService.soap",
           WellKnownObjectMode.Singleton);

      SampleService service = new SampleService();
      if(service == null)
      {
        Console.WriteLine("Service Not Found!");
      }
      else
      {
        Console.WriteLine("Calling Some Method on the server");
        Service.SomeMethod();
      }

      Console.WriteLine("** Press enter to end the client process. **");
      Console.ReadLine();
    }
  }
}

To expose a client-activated object, replace the appropriate area in Listing 39.3 with the following code snippet:

RemotingConfiguration.RegisterActivatedClientType(
   GetType(SampleService), "tcp://localhost:9000");

Registering with a Configuration File

Registering the remote object with a configuration file is fairly trivial. Simply make a call to the RemotingConfiguration.Configure method and pass in the configuration file and path. Listing 39.2 shows how to register a remote object using a config file. As with registering the remote object programmatically, there are slight differences between registering on the server and on the client. The following code snippet shows a sample configuration file for the server:

<configuration>
  <system.runtime.remoting>
    <application name="SampleServer">
      <service>
        <wellknown mode="SingleCall"
          type="SampleServer.SampleService, SampleService"
          objectUri="SampleServiceURI" />
      </service>
      <channels>
          <channel ref="tcp" port="9000" displayName="SampleService tcp channel" />
          <channel ref="http" port="9001" displayName="SampleService http channel" />
      </channels>
    </application>
  </system.runtime.remoting>
</configuration>

The following code snippet shows a sample configuration file for the client:

<configuration>
  <system.runtime.remoting>
    <application name="SampleServer">
      <service>
        <activated type="SampleServer.SampleService, SampleService"/>
      </service>
      <channels>
        <channel ref="tcp" port="9000" displayName="SampleService tcp channel" />
        <channel ref="http" port="9001" displayName="SampleService http channel" />
      </channels>
      <lifetime leaseTime="1000MS"
         sponsorshipTimeout="500MS" renewOnCallTime="100MS" leaseManagerPollTime="10MS" />
    </application>
  </system.runtime.remoting>
</configuration>

Listing 39.4. Registering a Remote Object Using a Config File
// Project Remoting Sample
// This code listing does not contain the sources for
// the sample remote object.  It is only meant to show
// how to expose the object to other application domains.

using System;

namespace SAMS.VisualCSharpDotNetUnleashed.Chapter39
{
  class RemotingSample
  {
    public static void Main()
    {
      HttpChannel httpChannel = new HttpChannel(9000);
      ChannelServices.RegisterChannel(httpChannel);

      RemotingConfiguration.Configure("SampleService.config");

      Console.WriteLine("** Press enter to end the server process. **");
      Console.ReadLine();
    }
  }
}

Building the Remoting Server Application

Building the server application consists of two things. In addition to the remote object that you want to host, you must also have a hosting application. As you will learn later in this chapter, you can use IIS to host your application, but for now, you will create a server host application. The only things required in this host application are to open a channel in which to host the remote object, register the type, and wait for a connection. Listing 39.5 shows the code necessary to create a host application that hosts and exposes the class SampleService.

Listing 39.5. The Server Host Application
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;

namespace SAMS.VisualCSharpDotNetUnleashed.Chapter39
{
  class RemotingSample
  {
    public static void Main()
    {
      HttpChannel httpChannel = new HttpChannel(9000);
      ChannelServices.RegisterChannel(httpChannel);

      RemotingConfiguration.RegisterWellKnownServiceType(typeof(SampleService),
        "SAMSSampleService/SampleService", WellKnownObjectMode.SingleCall);

      Console.WriteLine("** Press enter to end the server process. **");
      Console.ReadLine();
    }
  }
}

Building the Common Files

As stated in the previous section, there are two parts to hosting a remote object. The first part is a host application and the second part is the remote object itself. Listing 39.6 shows the code necessary to produce a simple class that can act as the remote object. This class has one method: GetInformation. When called, this method returns the application domain in which the object is created.

Listing 39.6. The Common File (Remote Object)
using System;

namespace SAMS.VisualCSharpDotNetUnleashed.Chapter39
{
  [Serializable]
  public class SampleService : System.MarshalByRefObject
  {
    public SampleService()
    {
    }

    public string GetInformation()
    {
      return "Thie application domain is: " + System.AppDomain.CurrentDomain.ToString();
    }
  }
}

Building the Client

The client application contains the most code. You must first open a channel. Remember that this is the client channel, not the channel on which the remote object is hosted, and therefore it must have a different port than the host. Next, you register the server type on the client. You can accomplish this by calling the RemotingConfiguration.Configure method or you can, as in Listing 39.6, programmatically register the type. After the type has been registered , you can activate the remote object. You can use the new operator or you can use the Activator.GetObject or Activator.CreateInstance methods to accomplish the activation. Listing 39.7 demonstrates the use of the new operator. Finally, you can call the method on the remote object. When called, this method returns the application domain in which the object was created and is then output to the console. Listing 39.7 shows the code necessary to create the client application.

Listing 39.7. The Client Console Application
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;

namespace SAMS.VisualCSharpDotNetUnleashed.Chapter39
{
  class ClientSample
  {
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main(string[] args)
    {
      // Create the client channel on a different port then the remote object.
      HttpChannel httpChannel = new HttpChannel(8085);
      ChannelServices.RegisterChannel(httpChannel);

      // Get the remote object
      RemotingConfiguration.RegisterWellKnownClientType(typeof(SampleService), 
        "http://localhost:9000/SAMSSampleService/SampleService.Soap");
      // Activate the remote object.
      SampleService service = new SampleService();
      if(service == null)
      {
        Console.WriteLine("Service Not Found!");
      }
      else
      {
        Console.WriteLine("Calling Some Method on the server");
        Console.WriteLine("");
        Console.WriteLine(service.GetInformation());
        Console.WriteLine("");
      }

      Console.WriteLine("** Press enter to end the client process. **");
      Console.ReadLine();
    }
  }
}

    Team LiB
    Previous Section Next Section