Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Applying Observer Pattern in .NET Remoting

0.00/5 (No votes)
10 Dec 2002 1  
This article describes the steps to develop simple chat applications using .NET remoting and the Observer pattern.

Sample Image - ChatClients.gif

Introduction

.NET Remoting allows objects or applications to communicate with one another in object oriented way. The applications can reside on the same computer or different computer. This article demonstrates the use of .NET Remoting to create simple client and server chat applications utilizing the Observer pattern.

The chat application consists of multiple clients and one server. The client, as shown above, is a GUI application, and the server is a console application. When a client sends a message, the message is routed to the server, and the server broadcasts the message to all of the connected clients.

This type of client and server relationships can be modelled by the observer pattern. Observer pattern also known as "publish and subscribe" design pattern, introduced by Gang of Four see Design Patterns: Elements of Reusable Object Oriented Software by E. Gamma, R. Helm, R. Johnson and J. Vlissides, defines a one-to-many dependency between a subject (observable) and any number of observers. When the subject changes state, all of its observers are notified and updated automatically. In response, each observer will query the subject to synchronize its state with the subject's state.

Observer Pattern

The following class diagram shows the relationship between the Subject and the Observer. The Subject may have one or more Observers, and it provides an interface to attaching and detaching the observer object at run time. The observer provides an update interface to receive signal from the subject. The ConcreteSubject stores the subject state interested by the observers, and it sends notification to it's observers. The ConcreteObserver maintains reference to a ConcreteSubject, and it implements an update operation.

The following collaboration diagram describes the interactions between the above objects in more detail. The ConcreteSubject notifies its observers whenever a change occurs that could make its observers' state inconsistent with its own. After being informed of a change in the concrete subject, a ConcreteObserver object may query the subject for information. ConcreteObserver uses this information to reconcile its state with that of Subject.

In C#, the subject and the observer interfaces can be defined as follows:

namespace Patterns.Observer
{
	public interface ISubject
	{
		void Attach( IObserver observer );
		void Detach( IObserver observer );

		bool Notify( string objType, short objState );
	}

	public interface IObserver
	{
		bool Update( ISubject sender, string objType, 
                             short objState );
	}
}

.NET Remoting

.NET remoting defines two types of objects activation, server activation and client activation. Server activated objects are created by the server when they are needed. There are two types of server activated objects, Singleton, and Single Call. Singleton objects are objects for which there will always be only one instance, regardless of how many clients there are for that object, and which have a default lifetime. SingleCall objects are created on each client method invocation. Therefore the lifetime of the SingleCall object is for the duration of the client call. Client-activated objects are objects whose lifetimes are controlled by the calling application domain, just as they would be if the object were local to the client.

Single Call objects are stateless where as Singleton objects share state for all clients. Client-activated objects maintain state on per-client basis. The client and server chat applications in this article use the Singleton server activation mode.

Any object can be changed into a remote object by deriving it from MarshalByRefObject. When a client activates a remote object, it receives a proxy to the remote object. All operations on this proxy are appropriately indirected to enable the remoting infrastructure to intercept and forward the calls appropriately.

The following classes, the ChatServerObject and ChatClientObject, inherit from MarshalByRefObject, and implements ISubject and IObserver respectively. These classes are created following the ConcreteSubject and ConcreteObserver classes as explained in the Observer Pattern section. ChatClientObject implements IObserver, therefore it can call the Attach function of ChatServerObject in order to register itself to the server, ChatServerObject. The member variable of ChatServerObject, clients contains the list of observers or ChatClientObject objects registered to ChatServerObject.

namespace ChatApplication
{
   public class ChatServerObject : MarshalByRefObject, ISubject
   {
      private ArrayList clients = new ArrayList(); 

      public void SetValue( string clientData )
      {
         Notify( clientData, 0 );
      }
         
      public void Attach( IObserver client )
      {
         Console.WriteLine( "observer attached" );
         clients.Add( client );         
      }

      public void Detach( IObserver client )
      {
         clients.Remove( client );
      }

      public bool Notify( string clientData, short objState )
      {
         for ( int i = 0; i < clients.Count; i++ )
            ((IObserver) clients[i]).Update( this, clientData, objState );

         return true;
      }
   }

   public class ChatClientObject : MarshalByRefObject, IObserver
   {
      private ArrayList newData = new ArrayList(); 

      public bool Update( ISubject sender, string data, short objState )
      {
         newData.Add( data );
         return true;
      }

      public int GetData( out string[] arrData )
      {
         int len = 0;

         arrData = new String[newData.Count];
         newData.CopyTo( arrData );
         newData.Clear();
         len = arrData.Length;

         return len;
      }

   }
}

Now we will create a server that hosts the ChatServerObject object. As mentioned above, the server is a console application. If /config is passed as the argument, the server will load the configuration from "server.config" file. Otherwise, it will configure the remoting parameters at run time.

At run time, it must first create a channel for communication between the subject and the observers, or the server and the clients. Channels are used to transport messages to and from remote objects. When a client calls a method on a remote object, the parameters, as well as other details related to the call, are transported through the channel to the remote object. Any results from the call are returned back to the client in the same way. A client can select any of the channels registered on the "server" to communicate with the remote object, A channel is a type that takes a stream of data, creates a package according to a particular network protocol, and sends the package to another computer. The channel can be one directional or bidirectional. .NET framework comes with two types of channels, TcpChannel and HttpChannel. In this application, the TCP channel is used, and it listens to port 9000.

After the TCP channel is created, it must be registered by calling ChannelServices.RegisterChannel( chan ). Then, the object type ChatApplication.ChatServerObject is registered by calling the function, RemotingConfiguration.RegisterWellKnownServiceType as an object Type on the service end as one that can be activated on request from a client. The last parameter of this call specifies that the activation type is Singleton.

class ChatServer
{
   [STAThread]
   static void Main(string[] args)
   {
      if ( args.Length == 1 )
      {
         if ( args[0].CompareTo("/config") == 0 )
         {
            RemotingConfiguration.Configure( "server.config" );
         }
      }
      else
      {
         TcpChannel chan = new TcpChannel( 9000 );
         ChannelServices.RegisterChannel( chan );
         RemotingConfiguration.RegisterWellKnownServiceType( 
            typeof(ChatApplication.ChatServerObject), 
            "ChatServer", WellKnownObjectMode.Singleton );
      }

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

The configuration file is in the XML form. The server configuration file "server.config" specifies the application name as "ChatServer". The service is well known service type "Singleton", and its URI is "Chatserver". The client can specify this URI when connecting to this object. In the "channel" element, port 9000, and "TCP" channel type are defined.

The default lifetime of this object is specified using the element. "leaseTime" attribute specifies the initial span of time that an object will remain in memory before the lease manager begins the process of deleting the object. The default is 5 minutes.

<configuration>
  <system.runtime.remoting>
    <application name="ChatServer">
      <service>
	<wellknown mode="Singleton" 
           type="ChatServerObject, ChatObjects" objectUri="Chatserver" />
      </service>
      <channels>
        <channel port="9000" ref="tcp" />
      </channels>
      <lifetime leaseTime="1M" renewOnCallTime="2M" />
    </application>
  </system.runtime.remoting>
</configuration>

The process of initializing the client application is very similar. The client application can load the remoting parameters contained in the configuration file, or or it can configure them at run time. To configure the remoting parameters at run time, the user can press the "Connect" button, and the following code will be executed.

It first checks whether the "use configuration" check box is checked. If it is, load the configuration from the file. Otherwise, register the TCP channel for communication with the server. A value of zero is passed in to TcpChannel to indicate full-duplex or bidirectional communication. The same as the server application above, the channel must be registered. The Activator.GetObject function call creates a proxy of the remoting object. In this case, it creates a proxy of type ChatApplication.ChatServerObject.

private void buttonConnect_Click(object sender, System.EventArgs e)
{
    if ( useConfigCheckBox.Checked )
    {
       RemotingConfiguration.Configure( "client.config" );

       chatServer = new ChatApplication.ChatServerObject();
    }
    else
    {
       if ( textServer.Text.Length <= 0 )
       {
          MessageBox.Show( "Please enter the target server name or address" );
          return;
       }
    
       chan = new TcpChannel(0);
       ChannelServices.RegisterChannel( chan );

       string url = String.Format( "tcp://{0}:9000/ChatServer", 
                                   textServer.Text );
       chatServer = (ChatApplication.ChatServerObject) 
                    Activator.GetObject( typeof( 
                      ChatApplication.ChatServerObject ), url );
    }            

       if ( chatServer == null )
          MessageBox.Show( "Could not locate server" );
       else
       {
          chatClient = new ChatApplication.ChatClientObject();

          try
          {
              chatServer.Attach( chatClient );
              buttonConnect.Enabled = false;
          }
          catch( SocketException sockExp )
          {
             MessageBox.Show( sockExp.Message );              
          }
       }                           
   }		
}

The client application has a timer which executes TimerOnTick function every 500 ms. The TimerOnTick function simply call chatClient.GetData function to retrieve all of the data from chatClient and then display them to the list.

void TimerOnTick( object obj, EventArgs ea )
{
    if ( chatClient != null )
    {
       string[] arrData;
       chatClient.GetData( out arrData );

       for ( int i = 0; i < arrData.Length; i++ )
          listData.Items.Add( arrData[i] );

       listData.SelectedIndex = listData.Items.Count-1;
    }
}

Summary

This article is an overview of .NET remoting. It shows how to develop a distributed application using .NET remoting, and applying the observer pattern in the process. I hope you enjoy this article as much as I enjoyed writing it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here