Previous column

Next article


Remoting in C# and .NET

Richard Wiener, Editor-in-Chief, JOT, Associate Professor, Department of Computer Science, University of Colorado at Colorado Springs

space COLUMN

PDF Icon
PDF Version


This is the first in a series of tutorial columns that shall deal with specialized aspects of C# programming and the .NET framework.

The .NET Remoting API is the equivalent of the Java Remote Method Invocation (RMI) API. Both frameworks allow objects on a client machine to communicate with remote objects on a server. To me, the infrastructure required in .NET appears simpler than in Java’s RMI.

What makes Remoting (or equivalently RMI) so attractive is that the low-level socket protocol that the programmer must normally manage is abstracted out. The programmer is able to operate at a much higher and simpler level of abstraction. In both languages there is some overhead in the form of boilerplate protocols that must be observed in order to setup the handshaking between the client and server machines. Once this is done, sending a message from a client machine to a server object uses the same syntax as sending a message to a local object. The metaphor of object-oriented programming remains central to this distributed programming.

My last column presented a distributed solution to the Traveling Salesperson Problem (see this column in the Nov/Dec, 2003 issue of JOT) using Java’s RMI.

Although it is tempting to present a detailed and exhaustive presentation of .NET’s distributed computing protocol, space does not permit this here. In this column, only an introduction and several simple examples of .NET Remoting using C# are presented.

The namespaces that one typically uses in C# distributed object applications are the following:

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;

When using one of the major IDE’s (Visual Studio or C# Builder), it is important to add the reference system.remoting.dll to your build if it is not already present.

In C#, using distributed objects does not require stubs or interfaces as in Java. The CLR (common language runtime) provides full support for remote object calls. Using distributed objects does not depend on the system registry for information about the remote classes. This information is encapsulated in a .DLL file that must be added as a reference when compiling the client code.

Classes derived from System.MarshalByRefObject cause the distributed object system to generate proxy objects on the client that encapsulate the low-level socket protocol. When the client sends a message to a remote object, it is the proxy that processes this message and sends serialized information across the network. The same works in reverse when proxy objects de-serialize information that is returned from the server.

Channel objects are the mechanism used to transfer messages between client and server. The .NET framework provides two bidirectional channels:

System.Runtime.Remoting.Channels.http.HttpChannel and
System.Runtime.Remoting.Channels.Tcp.TcpChannel.

The http channel uses SOAP (Simple Object Access Protocol) and the tcp channel uses a binary stream. This latter method is more efficient because it avoids the need to encode and decode SOAP messages.
A channel must be registered before it can be used. The ChannelServices class is used to accomplish this as follows:

ChannelServices.RegisterChannel(someChannel);

The general steps involved in writing a distributed application are summarized below.

Writing the Server

  1. Construct the server class.
  2. Select a method for hosting the server object(s) on the server. Typically a short application is created that launches the server and makes the server object available to the client(s).
  3. The server object typically waits for one or more client objects to communicate with it.

Writing the Client

  1. Identify the remote server object to the client.
  2. Connect the server to the client through a channel.
  3. The client must activate the remote object and create a reference to it.
  4. Communication to the remote object(s), once activated, is similar to sending messages to local objects.

Let us consider a simple client server application. The server uses a supporting NameHolder class that encapsulates an ArrayList of String objects, names, each holding a person’s name. The server has an AddName method that uses a String parameter to add another object to the names field within the NameHolder object.

The client application takes Console input as it is invoked and passes the string representing a person’s name to the server object serving as a proxy. It will be clear from the code how this is all accomplished. Let us examine the details of Listing 1.

Listing 1 – Simple Client/Server application using Remoting



Discussion and Analysis of Listing 1

The Client class uses the RemotingConfiguration.RegisterWellKnownClientType method as follows to connect itself to the server.

Here one computer is being used as a client and server.

The Client constructor creates an instance of the SaveNamesServer and passes this instance to its AddName method that uses the AddName method of the server (proxy) object to increase the size of the ArrayList in the NameHolder field by one.

The StartServer is used to launch the server process. It uses the same localhost socket as the Client. Using the Singleton mode ensures that the server object maintains state between client calls to this single server object.

It is essential that the NameHolder class be tagged as [Serializable] in order for the SaveNamesServer to be able to marshal its information properly.

Deployment

To deploy this distributed application, the following sequence of steps must be followed:

  1. Compile the server classes into a DLL as follows:

    csc /target:library SaveNamesServer.cs NameHolder.cs SaveNamesServer.dll
  2. Compile the StartServer application as follows:

    csc StartServer.cs /reference:SaveNamesServer.dll -> StartServer.exe
  3. Compile the Client application as follows:

    csc Client.cs /reference:SaveNamesServer.dll -> Client.exe
  4. Launch the StartServer application.
  5. Launch the Client application.

Each time the client is launched and a new name is specified on the command line, the previous names that were entered will be output.

Two GUI Clients and a Server Application

The next application of Remoting involves two client GUI’s running in separate processes that communicate with each other through a server object.

One of the GUI applications, Client2, periodically takes the coordinates of a square of size 50 from the server and moves a rectangle to this position (upper-left corner of the rectangle). The server changes this coordinate every two seconds so the square jumps from one location to another every two seconds.

The other GUI application, Client1, waits for the user to click the mouse button in a panel. A blue “X” marks the spot of the mouse click. Simultaneously, the spot at which the user clicked the mouse is marked with a small red “x” in Client2. The communication is done through the server. If the Client1 user clicks within the boundaries of the square that is slowing dancing around in Client2, a red “H” is shown in the panel of Client1. The cumulative hits and misses are also updated after each mouse click in Client1.

A screenshot of both client applications running and the server providing the communication channel as well as coordinates for the moving square in Client2 is shown below.

 

Listing 2 contains the C# classes that implement this Remoting application.

Listing 2 - Remoting application with a server and two GUI clients





Discussion and Analysis of Listing 2

The TargetServer spawns a thread and starts creating xPos and yPos values every two seconds. The Client1 application communicates with this server through the Record method. Because the TargetServer is declared a subclass of MarshalByRefObject, communication through proxy objects is accomplished for both Client1 and Client2. Each of these GUI applications holds a reference to this proxy object as a server field.

Chat Session Application

The final application is a simple chat client/server application.

Any number of client applications, each containing a GUI that allows text to be added to the existing session, should permit communication among all clients that have logged in. All clients are updated on all communications every second.

The application is designed so that each client must know about the server, but the server does not know about any of the clients. The server’s responsibility is to maintain a centralized store of the clients that are logged in as well as the text for the entire chat session by adding text to it whenever a client posts a new message by clicking the “Send Text” button.

Each client, through a thread, polls the server every second to update the list of clients and to update the overall session text. Therefore the server does not need to know about any of the clients.

Two screen shots that depict a session between two clients before and after one has logged off are shown below.

 

The C# code for this application is given in Listing 3.

Listing 3 – Chat Session Client/Server Application Using Remoting





Discussion and Analysis of Listing 3

The ChatServer class holds two serializable fields, clients, an ArrayList, and chatSession, a String.

The ChatClient class is more complex because of the GUI. It contains a server field of type ChatServer. When each client application is launched, a command-line argument (string) that contains the name of the client (Richard and Erik in the example above) must be supplied to the program. This important identification is the only means that clients have to know who is logged-on and who they can “talk” to.

When the user clicks the “Login” button, code that enables the client to be connected to the server and the server object activated is executed. Since the server object is a singleton, its state is maintained between calls from various clients.

All calls to the remote server object are shown in blue. MarshalByRefObject is used to ensure that information is transmitted to the server through a proxy through serialization and not by value. If marshal by value were used (by removing the inheritance from class MarshalByRefObject), each client would have its own independent copy of the server object and would not be updated when another client added text or removed itself from the system.

 

About the author

Losavio

space Richard Wiener is Associate Professor of Computer Science at the University of Colorado at Colorado Springs. He is also the Editor-in-Chief of JOT and former Editor-in-Chief of the Journal of Object Oriented Programming. In addition to University work, Dr. Wiener has authored or co-authored 21 books and works actively as a consultant and software contractor whenever the possibility arises.

Cite this column as follows: Richard Wiener: “Remoting in C# and .NET”, in Journal of Object Technology, vol. 3, no. 1, January-February 2004, pp. 83-100. http://www.jot.fm/issues/issue_2004_01/column8


Previous column

Next article