[ Home | Syllabus | Course Notes | Assignments | Search]
ASP.NET good for thin clients (access through any machine supporting a HTML browser
B2B needs "rich" clients that can so something with the data that they get from the server
Can build "rich" clients using web services BUT HTTP/SOAP is not the most efficient.
.NET remoting replaces DCOM and COBRA for efficient remote access
Derive your class from "MarshalByRefObject" to make a remote object
We have already seen the use of a proxy with web service access (derived from WSDL)
To access a remote object need to register it using either "RegisterActivatedServiceType" or "RegisterWellKnownServiceType" for example
RemotingConfiguration.RegisterWellKnownServiceType ( typeof (RemotableClass), // Remotable class "RemoteObject", // URI of remotable class WellKnownObjectMode.SingleCall // Activation mode );
Also need to create a register a channel to access this object - can be either TCP or HTTP channel
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
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>
The previous examples used server side activation of the objects. There is also client activation - differences are
Server activated only creates object when the client proxy makes first request NOT on "new" statement.
Client activated creates object on the "new" statement
Client activated objects can use non-default constructors, server activated cannot
Server activation allows "singleton" objects - objects that retain state and can be shared among clients
Client activated objects preserve state but only for that client
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"); } }
server activated singlecall objects live only for the one method call
Singleton server activated and client activated use leases to control 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 |
|
The second half of this chapter builds on what you’ve learned and enriches your understanding of .NET remoting. In it, you’ll learn:
How to use IIS as an activation agent in order to avoid having to manually start server processes running
How to combine HTTP channels with binary formatters to increase efficiency on the wire
How to use events and delegates with remotely activated objects
How to place asynchronous method calls to remote objects
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
Usually IIS is running (can start up when machine is booted - does not require anyone to be logged in)
ISS can perform authetication/authorization
Can use secure socket for privacy
IIS does port negotiation
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 ();
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
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 (); } }
Web.config registers the Paper class so that it can be activated remotely. Activation is performed by IIS. Web.config registers a two-way HTTP channel accompanied by binary formatters. The two-way channel enables Paper to receive calls from its clients and fire events to them as well. The binary formatters increase the channel’s efficiency on the wire.
At startup, NetDraw registers a channel of its own and registers Paper in the local application domain using NetDraw.exe.config. That file contains the remote object’s URL (http://localhost/NetDraw/Paper.rem). It also registers a two-way HTTP channel with binary formatters so that the client can place calls to remote Paper objects and receive events fired by those objects.
Paper’s DrawStroke method is a one-way method, enabling clients to fire off calls to it without waiting for the calls to return. Synchronous calls to DrawStroke would produce a sluggish user interface. Imagine 1000 instances of NetDraw connected over a slow network and you’ll see what I mean.
Callbacks emanating from events fired by remote objects execute on threads provided by the .NET Framework. To make sure that its main thread can’t access Strokes while OnNewStroke updates it (or vice versa), NetDraw uses C#’s lock keyword to synchronize access to Strokes.
Stroke objects accompany DrawStroke methods and NewStroke events as method parameters. So that instances of Stroke can travel between application domains (and, by extension, between machines), the Stroke class includes a Serializable attribute. That enables the .NET Framework to serialize a Stroke into the channel and rehydrate it on the other side. Stroke is a marshal-by-value (MBV) class, whereas Paper is marshal-by-reference. MBV means no proxies are created; instead, the object is copied to the destination.
The Paper class overrides InitializeLifetimeService and returns null so that Paper objects won’t disappear if clients go for 5 minutes without calling them. As an alternative, Paper could simply set its lease’s RenewOnCallTime property to something greater than 5 minutes—say, an hour.
F1: TCP port number
F2: registers the "Clock" class named "Clock" (simple URI) for a SingleCall (more on this later)
F3: This program must run to keep the connection alive - once terminated, the remote object is no longer available
F4: URI for the remote object (which happens to be on the localhost) gives host name/port for TCP connection, service name must match
F5: I changed the name on the URI to "RemoteClock" to distinguish it from the class name
F6: no need to activation mode or class name
F7: .rem extension calls Aspnet_isapi.dll which does the work of activation
F8: allows two way communication
F9: registers the delegate event handler for this event (we have seen this before)
F10: port "0" lets server pick the port number for the call back
F11: uses IIS remote object activation
F12: more efficient than soap body in HTTP request/response
F13: singleton objects are shared by all clients
F14: change from book to work with increased security of .NET framework 1.1
F15: changes the lease lifetime
F16: client sends stroke - no response is necessary
F17: need in order to send object over network