Windows Systems Programming: Spring 2004

[ Home | Syllabus | Course Notes | Assignments | Search]


.Net remoting   


A Simple Example

Let's build a simple "clock" remote object

Code for the remote class requires minor changes from a local class

using System;

public class Clock : MarshalByRefObject
{
    public string GetCurrentTime ()
    {
        return DateTime.Now.ToLongTimeString ();
    }
}

To register and make this object available use the following program

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

class MyApp
{
    static void Main ()
    {
        TcpServerChannel channel = new TcpServerChannel (1234);
        ChannelServices.RegisterChannel (channel);

        RemotingConfiguration.RegisterWellKnownServiceType
            (typeof (Clock), "RemoteClock", WellKnownObjectMode.SingleCall); 

        Console.WriteLine ("Press Enter to terminate..."); 
        Console.ReadLine ();
    }
}

Here is a client that accesses this remote object

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

class MyApp
{
    static void Main ()
    {
        TcpClientChannel channel = new TcpClientChannel ();
        ChannelServices.RegisterChannel (channel);

        RemotingConfiguration.RegisterWellKnownClientType
            (typeof (Clock), "tcp://localhost:1234/RemoteClock");

        Clock clock = new Clock ();
        Console.WriteLine (clock.GetCurrentTime ());
    }
}

To compile these programs use

csc /t:library clockserver.cs
csc /r:clockserver.dll timeserver.cs
csc /r:clockserver.dll timeclient.cs


Declarative Configuration

The previous example required program changes to access a different remote object or to change the port numbers. Here is a solution where this information is kept in configuration files that can be changed by editing the files.

The Clock Object program is the same

The TimeServer program uses a configuration file

using System;
using System.Runtime.Remoting;

class MyApp
{
    static void Main ()
    {
        RemotingConfiguration.Configure ("TimeServer.exe.config");
        Console.WriteLine ("Press Enter to terminate...");
        Console.ReadLine ();
    }
}
Which looks like this
<configuration>
  <system.runtime.remoting>
    <application>
      <service>
        <wellknown mode="SingleCall" type="Clock, ClockServer"
          objectUri="RemoteClock" />
      </service>
      <channels>
        <channel ref="tcp server" port="1234" />
      </channels>
    </application>
  </system.runtime.remoting>
</configuration>
The client program looks like this
using System;
using System.Runtime.Remoting;

class MyApp
{
    static void Main ()
    {
        RemotingConfiguration.Configure ("TimeClient.exe.config");
        Clock clock = new Clock ();
        Console.WriteLine (clock.GetCurrentTime ());
    }
}
Using this configuration file
<configuration>
  <system.runtime.remoting>
    <application>
      <client>
        <wellknown type="Clock, ClockServer"
          url="tcp://localhost:1234/RemoteClock" />
      </client>
      <channels>
        <channel ref="tcp client" />
      </channels>
    </application>
  </system.runtime.remoting>
</configuration>

A simple change will allow access on sensor

<configuration>
  <system.runtime.remoting>
    <application>
      <client>
        <wellknown type="Clock, ClockServer"
          url="tcp://sensor.cs.odu.edu:1234/RemoteClock" />
      </client>
      <channels>
        <channel ref="tcp client" />
      </channels>
    </application>
  </system.runtime.remoting>
</configuration>

Server Activation vs. Client Activation

The previous examples used server side activation of the objects. There is also client activation - differences are


Client Activation Example

STOPWATCH: requirement to preserve state between calls to Start and Stop. Don't want to share this object with other clients

The object itself

using System;

public class Stopwatch : MarshalByRefObject
{
    DateTime mark = DateTime.Now;

    public void Start ()
    {
        mark = DateTime.Now;
    }

    public int Stop ()
    {
        return (int) ((DateTime.Now - mark).TotalMilliseconds);
    }
}

The server registration code

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

class MyApp
{
    static void Main ()
    {
        TcpServerChannel channel = new TcpServerChannel (1234);
        ChannelServices.RegisterChannel (channel);

        RemotingConfiguration.RegisterActivatedServiceType
            (typeof (Stopwatch));

        Console.WriteLine ("Press Enter to terminate...");
        Console.ReadLine ();
    }
}

The client

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

class MyApp
{
    static void Main ()
    {
        TcpClientChannel channel = new TcpClientChannel ();
        ChannelServices.RegisterChannel (channel);

        RemotingConfiguration.RegisterActivatedClientType
            (typeof (Stopwatch), "tcp://localhost:1234");

        Stopwatch sw = new Stopwatch ();
        sw.Start ();

        Console.WriteLine ("Press Enter to show elapsed time...");
        Console.ReadLine ();

        Console.WriteLine (sw.Stop () + " millseconds");
    }
}

Remote Object Lifetimes

The following ILease properties govern the lifetime of the object with which a lease is associated:

Property

Description

Get

Set

InitialLeaseTime

Length of time following activation that the object lives if it receives no method calls

RenewOnCallTime

Minimum value that CurrentLeaseTime is set to each time the object receives a call

CurrentLeaseTime

Amount of time remaining before the object is deactivated if it doesn’t receive a method call

 


Advanced Remoting

The second half of this chapter builds on what you’ve learned and enriches your understanding of .NET remoting. In it, you’ll learn:

In previous examples, the server side registration process had to be running to access a remote object - not practical with many possible remote object.

Could build your object activation as a service and set it up to run when the systems starts - also not practical with many objects

Can use IIS as an activation agent

Here is a configuration file to register a remote object for IIS activation using server side activation

<configuration>
  <system.runtime.remoting>
    <application>
      <service>
        <wellknown mode="SingleCall" type="Clock, ClockServer"
          objectUri="Clock.rem" />
      </service>
    </application>
  </system.runtime.remoting>
</configuration>

Clients must use the HTTP channel object - for example

HttpClientChannel channel = new HttpClientChannel ();
ChannelServices.RegisterChannel (channel);
RemotingConfiguration.RegisterWellKnownClientType
    (typeof (Clock), "http://localhost/MyClock/Clock.rem");
Clock clock = new Clock ();
 

Client-activated objects are registered and activated differently. This Web.config file registers Clock as a client-activated object rather than a server-activated one:

<configuration>
  <system.runtime.remoting>
    <application>
      <service>
        <activated type="Clock, ClockServer" />
      </service>
    </application>
  </system.runtime.remoting>
</configuration>

And here’s how a remote client would activate it, once more assuming Clock resides in a virtual directory named MyClock on the local machine:

HttpClientChannel channel = new HttpClientChannel ();
ChannelServices.RegisterChannel (channel);
RemotingConfiguration.RegisterActivatedClientType
    (typeof (Clock), "http://localhost/MyClock");
Clock clock = new Clock ();

 


Events and Delegates (callbacks)

Adds symmetry between client and server (client can call server object method, server object can call back client method on event)

Suppose you built a Clock class that fires a NewHour event at the top of every hour. Here’s how that class—and a delegate defining the signature of NewHour handlers—might be declared:

public delegate void NewHourHandler (int hour);

public class Clock : MarshalByRefObject
{
    public event NewHourHandler NewHour;
      ...
}

Here’s a Web.config file that registers Clock for remote activation as a server-activated singleton using IIS:

<configuration>
  <system.runtime.remoting>
    <application>
      <service>
        <wellknown mode="Singleton" type="Clock, ClockServer"
          objectUri="Clock.rem" />
      </service>
      <channels>
        <channel ref="http" />
      </channels>
    </application>
  </system.runtime.remoting>
</configuration>

Here’s client code to create a Clock instance and register a handler for NewHour events:

RemotingConfiguration.Configure ("Client.exe.config");
Clock clock = new Clock ();
clock.NewHour += new NewHourHandler (OnNewHour);
  .
  .
  .
public void OnNewHour (int hour)
{
    // NewHour event received
}

And here’s the CONFIG file referenced in the client’s code, which assumes that Clock is deployed in a virtual directory named MyClock:

<configuration>
  <system.runtime.remoting>
    <application>
      <client>
        <wellknown type="Clock, ClockServer"
          url="http://localhost/MyClock/Clock.rem" />
      </client>
      <channels>
        <channel ref="http" port="0" />
      </channels>
    </application>
  </system.runtime.remoting>
</configuration>

Server needs metadata about client's callback


NetDraw

Here is a window form which uses a shared object

using System;
using System.Collections;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.Remoting;
using System.ComponentModel;

class MyForm : Form
{
    Paper VirtualPaper; //// remote object
    Stroke CurrentStroke = null; //// remote object
    ArrayList Strokes = new ArrayList ();
    StrokeHandler NewStrokeHandler;

    MyForm ()
    {
        Text = "NetDraw";

        try {
            // Configure the remoting infrastructure
            RemotingConfiguration.Configure ("NetDraw.exe.config");

            // Create a remote Paper object
            VirtualPaper = new Paper (); // creates/connects to remote object

            // Connect a handler to the object's NewStroke events
            NewStrokeHandler = new StrokeHandler (OnNewStroke); // event handler for strokes draw by other clients
            VirtualPaper.NewStroke += NewStrokeHandler;
        }
        catch (Exception ex) {
            MessageBox.Show (ex.Message);
            Close ();
        }
    }

    protected override void OnPaint (PaintEventArgs e)
    {
        lock (Strokes.SyncRoot) {
            // Draw all currently recorded strokes
            foreach (Stroke stroke in Strokes)
                stroke.Draw (e.Graphics);
        }
    }

    protected override void OnMouseDown (MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left) {
            // Create a new Stroke and assign it to CurrentStroke
            CurrentStroke = new Stroke (e.X, e.Y);
        }
    }

    protected override void OnMouseMove (MouseEventArgs e)
    {
        if ((e.Button & MouseButtons.Left) != 0 &&
            CurrentStroke != null) {
            // Add a new segment to the current stroke
            CurrentStroke.Add (e.X, e.Y);
            Graphics g = Graphics.FromHwnd (Handle);
            CurrentStroke.DrawLastSegment (g); // to show stroke immediately
            g.Dispose ();
        }
    }

    protected override void OnMouseUp (MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left && CurrentStroke != null) {
            // Complete the current stroke
            if (CurrentStroke.Count > 1) {
                // Let other clients know about it, too
                VirtualPaper.DrawStroke (CurrentStroke); // notifies other clients by putting stroke in the Paper object
            }
            CurrentStroke = null;
        }
    }

    protected override void OnKeyDown (KeyEventArgs e)
    {
        if (e.KeyCode == Keys.Delete) {
            // Delete all strokes and repaint
            lock (Strokes.SyncRoot) { // prevent call to event handler simultaneously	
                Strokes.Clear ();
            }    
            Invalidate ();
        }
    }

    protected override void OnClosing (CancelEventArgs e)
    {
        // Disconnect event handler before closing
        base.OnClosing (e);
        VirtualPaper.NewStroke -= NewStrokeHandler; //// deregister this event handler
    }

    public void OnNewStroke (Stroke stroke)
    {
        // Record and display a stroke drawn in a remote client
        lock (Strokes.SyncRoot) {
            Strokes.Add (stroke);
        }
        Graphics g = Graphics.FromHwnd (Handle);
        stroke.Draw (g);
        g.Dispose ();
    }

    static void Main ()
    {
        Application.Run (new MyForm ());
    }
}

 

The configuration file

<configuration>
  <system.runtime.remoting>
    <application>
      <client>
        <wellknown type="Paper, PaperServer"
          url="http://localhost/cs477/Book1/Chap15/NetDraw/Paper.rem" />
      </client>
      <channels>
        <channel ref="http" port="0">
          <clientProviders>
            <formatter ref="binary" /> 
          </clientProviders>
          <serverProviders>
            <formatter ref="binary" />
          </serverProviders>
        </channel>
      </channels>
    </application>
  </system.runtime.remoting>
</configuration>

Here is the web config file used by IIS activation to instantiate and register this object

<configuration>
  <system.runtime.remoting>
    <application>
      <service>
        <wellknown mode="Singleton" type="Paper, PaperServer"
          objectUri="Paper.rem" />
      </service>
      <channels>
        <channel ref="http">
          <clientProviders>
            <formatter ref="binary"/>
          </clientProviders>
          <serverProviders>
      <formatter ref="binary" typeFilterLevel="Full" />
          </serverProviders>
        </channel>
      </channels>
    </application>
  </system.runtime.remoting>
</configuration>

The remote objects are defined here

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.Remoting.Messaging;
using System.Collections;

public delegate void StrokeHandler (Stroke stroke);

public class Paper : MarshalByRefObject
{
    public event StrokeHandler NewStroke;

    public override object InitializeLifetimeService ()
    {
        return null;
    }

    [OneWay]
    public void DrawStroke (Stroke stroke)
    {
        if (NewStroke != null)
            NewStroke (stroke); // event to send stroke to other clients
    }
}

[Serializable]
public class Stroke
{
    ArrayList Points = new ArrayList ();

    public int Count
    {
        get { return Points.Count; }
    }

    public Stroke (int x, int y)
    {
        Points.Add (new Point (x, y));
    }

    public void Add (int x, int y)
    {
        Points.Add (new Point (x, y));
    }

    public void Draw (Graphics g)
    {
        Pen pen = new Pen (Color.Black, 8);
        pen.EndCap = LineCap.Round;
        for (int i=0; i<Points.Count - 1; i++)
            g.DrawLine (pen, (Point) Points[i], (Point) Points[i + 1]);
        pen.Dispose ();
    }

    public void DrawLastSegment (Graphics g)
    {
        Point p1 = (Point) Points[Points.Count - 2];
        Point p2 = (Point) Points[Points.Count - 1];
        Pen pen = new Pen (Color.Black, 8);
        pen.EndCap = LineCap.Round;
        g.DrawLine (pen, p1, p2);
        pen.Dispose ();
    }
}

 


 


Copyright chris wild 1999-2004.
For problems or questions regarding this web contact [Dr. Wild].
Last updated: April 07, 2004.