Remoting in C# and .NET
Richard Wiener, Editor-in-Chief,
JOT, Associate Professor, Department of Computer Science, University
of Colorado at Colorado Springs
|
 |
COLUMN

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
- Construct the server class.
- 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).
- The
server object typically waits for one or more client objects
to communicate with it.
Writing the Client
- Identify the remote server object to the client.
- Connect the
server to the client through a channel.
- The client must activate
the remote object and create a reference to it.
- 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:
- Compile the server classes into a DLL as follows:
csc /target:library
SaveNamesServer.cs NameHolder.cs SaveNamesServer.dll
- Compile the
StartServer application as follows:
csc StartServer.cs /reference:SaveNamesServer.dll
-> StartServer.exe
- Compile the Client application as follows:
csc
Client.cs /reference:SaveNamesServer.dll -> Client.exe
- Launch the StartServer application.
- 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

|
 |
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
|