Advanced Remoting

Advanced Remoting

The first half of this chapter imparted the fundamental knowledge you need to activate managed objects remotely. You learned how to make classes remotable, how to register them for remote activation, and how to activate them. You learned about the difference between server-activated objects and client-activated objects, and you learned about the two activation modes single-call and singleton supported by server-activated objects. You also learned about the role that channels play in remote activation and how leases control a remote object s lifetime.

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

This part of the chapter concludes with a distributed drawing application that ties together many of the concepts introduced in the following pages.

Using IIS as an Activation Agent

One of the most striking differences between .NET remoting and DCOM is that the former offers no support for automatically launching server processes. Someone has to start the server process running so that it can register classes for remote activation and listen for activation requests. This behavior contrasts starkly with that of DCOM, which starts new server processes on request when remote clients call CoCreateInstanceEx or other activation API functions.

.NET remoting offers two ways for you to avoid having to manually start server processes. Option number 1 is to implement the server application as a service. You can write a service by deriving from System.ServiceProcess.ServiceBase and overriding key virtual methods such as OnStart and OnStop. The benefit to implementing a remoting server as a service is that you can configure a service to start automatically each time the system starts up. A service can also run absent a logged-on user, meaning that after auto-starting, it s always running and always available even when no one is logged in at the server console.

Option number 2 is to use IIS as an activation agent. IIS is a service itself and is always up and running on most Web servers. Moreover, IIS is capable of responding to requests from remote clients who want to activate objects on the server using the .NET remoting infrastructure. Using IIS as an activation agent has several advantages:

  • You don t have to write a server application to register remotable classes and listen for activation requests; IIS is the server application.

  • You can use IIS to authenticate remote callers and also to safeguard data with Secure Sockets Layer (SSL).

  • You can use IIS for port negotiation. If you deploy two conventional server applications on the same machine, it s up to you to ensure that the applications use different port numbers. With IIS as the host, however, IIS picks the port numbers, which simplifies deployment and administration.

IIS supports both server-activated objects and client-activated objects. Classes remoted with IIS s help can be registered programmatically (in Global.asax) or declaratively (in Web.config). The following Web.config file registers the Clock class in Figure 15-2 for remote activation using IIS:

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

Note Clock s URI: Clock.rem. URIs registered with IIS must end in .rem or .soap because both extensions are mapped to Aspnet_isapi.dll in the IIS metabase and to the .NET remoting subsystem in Machine.config. You can register additional extensions if you d like, but these are the only two that work out of the box.

Objects activated with IIS always use HTTP channels to communicate with remote clients. Clients must also register HTTP channels. Here s how a client would create a Clock instance, assuming Clock resides in a virtual directory named MyClock on the local machine:

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

Notice that no port number is specified anywhere neither on the client nor on the server. IIS picks the port numbers.

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 ();

Be aware that IIS client activation requires the remotable class to be hosted in a virtual directory other than wwwroot. You can t use IIS to activate a client-activated object by putting a Web.config file registering the class in wwwroot and the DLL that implements the class in wwwroot\bin. Instead, you must install Web.config in a separate virtual directory (for example, MyClock) and the DLL in the bin subdirectory (MyClock\bin). Only then will IIS activate a client-activated object.

HTTP Channels and Binary Formatters

One drawback to using IIS as an activation agent is that you have no choice but to use HTTP channels to link application domains. HTTP is a higher-level protocol than TCP and is also less efficient on the wire. Furthermore, HTTP channels encode calls as SOAP messages, which increases the verbosity of message traffic.

Fortunately, the .NET remoting infrastructure uses a pluggable channel architecture that lets you choose the channel type as well as the format in which messages are encoded by the chosen channel. IIS supports only HTTP channels, but it doesn t require the channel to encode calls as SOAP messages. HTTP channels use SOAP by default because they use formatters named SoapClientFormatterSinkProvider and SoapServerFormatterSinkProvider to serialize and deserialize messages. You can replace these formatters with instances of BinaryClientFormatterSinkProvider and BinaryServerFormatterSinkProvider and encode messages in a more compact binary format. Binary messages utilize network bandwidth more efficiently and still allow you to use IIS as the activation agent.

The following Web.config file registers Clock to be activated by IIS as a single-call server-activated object. It also replaces the default SOAP formatter with a binary formatter on the server side. Changes are highlighted in bold:

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

A client that wants to activate instances of Clock that are registered in this way must pair a client-side HTTP channel with a binary formatter too. The following example demonstrates how a client can configure the channel programmatically and then activate a remote instance of Clock:

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

A client that prefers declarative configuration would do it this way instead:

RemotingConfiguration.Configure ("Client.exe.config"); Clock clock = new Clock ();

Here are the contents of Client.exe.config:

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

Combining HTTP channels with binary formatters lets you have your cake and eat it too. Using similar tactics, you could combine TCP channels with SOAP formatters to encode message traffic as SOAP messages. You can even build formatters of your own and plug them into existing channels. The modular nature of the .NET Framework s remoting infrastructure makes all sorts of interesting extensions possible without requiring you to write a ton of code or replace portions of the framework that you have no desire to replace.

Delegates and Events

One of the hallmarks of the .NET Framework s type system is that it elevates events to first-class type members along with methods, properties, and fields. Better still, the framework s event infrastructure works with remote objects as well as local objects. A client connects event handlers to events fired by remote objects using the very same syntax that it uses to connect handlers to events fired by local objects. The only catch is that the client must register a server channel as well as a client channel so that it can receive event callbacks. By the same token, the server, which normally registers only a server channel, must register a client channel too so that it can fire events to remote clients.

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>

Note the ref attribute accompanying the channel element. The value http instantiates a two-way HttpChannel object instead of a one-way HttpServerChannel. The two-way channel is necessary if Clock is to receive calls from remote clients and fire events to them as well.

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>

The client also registers a two-way HttpChannel, and it specifies a port number of 0. The 0 configures the channel to listen for callbacks but permits the .NET Framework to pick the port number.

A client that receives events from remote objects must have access to metadata describing the objects. In addition, the objects must have metadata describing the client at least the client components that contain the callback methods. The practical implication is that if you deploy clients on one machine and remote classes on another, you need to put the clients binaries in the directory that holds the server components and vice versa.

Asynchronous Method Calls

By default, calls to remote objects are synchronous. A thread that places a synchronous call blocks until the call returns. If the call takes a long time to find its way to the recipient or the recipient takes a long time to process the call once it arrives, the caller waits for a long time as well.

That s why the .NET Framework supports asynchronous method calls. Asynchronous method calls aren t limited to remote objects; they work with local objects too. They re enacted through asynchronous delegates, which make placing asynchronous calls almost as easy as placing synchronous ones. Asynchronous calls return immediately, no matter how long they take to reach their recipients or how long the recipients take to process them.

Suppose a remote object has a CountPrimes method similar to the one in Figure 14-2. Counting primes is a CPU-intensive task that can take a long time to complete. Calling CountPrimes as in the following takes more than 10 seconds on my PC a 1.4 GHz Pentium with 384 MB of memory:

int count = sieve.CountPrimes (100000000);

If called through an asynchronous delegate, however, CountPrimes returns immediately. To call CountPrimes asynchronously, you first declare a delegate whose signature matches CountPrimes signature, as shown here:

delegate int CountPrimesDelegate (int max);

You then wrap an instance of the delegate around CountPrimes and call the delegate s BeginInvoke method:

CountPrimesDelegate del = new CountPrimesDelegate (sieve.CountPrimes); IAsyncResult ar = del.BeginInvoke (100000000, null, null);

To retrieve the value that CountPrimes returns, you later complete the call by calling the delegate s EndInvoke method and passing in the IAsyncResult returned by BeginInvoke:

int count = del.EndInvoke (ar);

If CountPrimes hasn t returned when EndInvoke is called, EndInvoke blocks until it does. Calling BeginInvoke and EndInvoke in rapid succession is morally equivalent to calling CountPrimes synchronously.

Can a client determine whether an asynchronous call has completed before calling EndInvoke? You bet. The IsCompleted property of the IAsyncResult that BeginInvoke returns is true if the call has completed, false if it has not. The following code snippet calls EndInvoke if and only if the call has completed:

CountPrimesDelegate del = new CountPrimesDelegate (sieve.CountPrimes); IAsyncResult ar = del.BeginInvoke (100000000, null, null); . . . if (ar.IsCompleted) { int count = del.EndInvoke (ar); } else { // Try again later }

A client can also use IAsyncResult s AsyncWaitHandle property to retrieve a synchronization handle. A thread that calls WaitOne on that handle blocks until the call completes.

A client can also ask to be notified when an asynchronous call completes. Completion notifications enable a client to learn when a call completes without polling IsCompleted. The basic strategy is to wrap a callback method in an instance of System.AsyncCallback and pass the resulting delegate to BeginInvoke. When the call completes, the callback method is called. Here s an example:

CountPrimesDelegate del = new CountPrimesDelegate (sieve.CountPrimes); AsyncCallback ab = new AsyncCallback (PrimesCounted); IAsyncResult ar = del.BeginInvoke (100000000, ab, null); . . . void PrimesCounted (IAsyncResult ar) { // CountPrimes completed }

After the callback method is called, you still need to complete the call by calling EndInvoke. Only by calling EndInvoke can you get the results of the call and let the system clean up after itself following a successful asynchronous call. You can call EndInvoke from inside the callback method if you d like.

One-Way Methods

The .NET remoting infrastructure supports a slightly different form of asynchronous method calls that rely on entities known as one-way methods. A one-way method has input parameters only no out or ref parameters are allowed or no parameters at all, and it returns void. You designate a method as a one-way method by tagging it with a OneWay attribute:

[OneWay] public void LogError (string message) { ... }

OneWay is shorthand for OneWayAttribute, which is an attribute class defined in the System.Runtime.Remoting.Messaging namespace.

Calls to one-way methods execute asynchronously. You don t get any results back, and you aren t notified if the method throws an exception. You don t even know for sure that a one-way method call reached the recipient. One-way methods let you place calls using fire-and-forget semantics, which are appropriate when you want to fire off a method call, you don t want any results back, and the method s success or failure isn t critical to the integrity of the application as a whole. Sound intriguing? The application in the next section uses one-way methods to fire notifications to remote objects without affecting the liveliness of its user interface. As you work with .NET remoting, you ll probably find additional uses for one-way methods.

Putting It All Together: The NetDraw Application

Let s close with an application that assembles many of the concepts outlined in this chapter into one tidy package. The application is shown in Figure 15-5. It s a distributed drawing program that links clients together so that sketches drawn in one window appear in the other windows, too. Before you try it, you need to deploy it. Here s how:

  1. Build the application s binaries. Here are the commands:

    csc /t:library paperserver.cs csc /t:winexe /r:paperserver.dll netdraw.cs

    These commands produce binaries named PaperServer.dll and NetDraw.exe. PaperServer.dll implements a remotable class named Paper and a utility class named Stroke. It also declares a delegate that clients can use to wrap handlers for the NewStroke events that Paper fires. NetDraw.exe is a Windows Forms application that serves as a remote client to Paper objects.

  2. Create a virtual directory named NetDraw on your Web server. Copy Web.config to the NetDraw directory. Create a bin subdirectory in the NetDraw directory and copy both NetDraw.exe and PaperServer.dll to bin.

  3. Create another directory somewhere on your Web server (it needn t be a virtual directory) and copy NetDraw.exe, NetDraw.exe.config, and PaperServer.dll to it.

Now start two instances of NetDraw.exe and scribble in one of them by moving the mouse with the left button held down. Each time you release the button, the stroke that you just drew should appear in the other NetDraw window. If you d like to try it over a network, simply move NetDraw.exe, NetDraw.exe.config, and PaperServer.dll to another machine and modify the URL in NetDraw.exe.config to point to the remote server.

Figure 15-5

The NetDraw application.

NetDraw, whose source code appears in Figure 15-6, demonstrates several important principles of .NET remoting. Let s tackle the big picture first. At startup, the client NetDraw.exe instantiates a Paper object and registers a handler for the object s NewStroke events:

VirtualPaper = new Paper (); NewStrokeHandler = new StrokeHandler (OnNewStroke); VirtualPaper.NewStroke += NewStrokeHandler;

Upon completion of each new stroke drawn by the user (that is, when the mouse button comes up), NetDraw calls the Paper object s DrawStroke method and passes in a Stroke object containing a series of x-y coordinate pairs describing the stroke:

VirtualPaper.DrawStroke (CurrentStroke);

DrawStroke, in turn, fires a NewStroke event to all clients that registered handlers for NewStroke events. It passes the Stroke provided by the client in the event s parameter list:

public void DrawStroke (Stroke stroke) { if (NewStroke != null) NewStroke (stroke); }

The event activates each client s OnNewStroke method, which adds the Stroke to a collection of Stroke objects maintained by each individual client and draws the stroke on the screen. Because Paper is registered as a singleton, all clients that call new on it receive a reference to the same Paper object. Consequently, a stroke drawn in one client is reported immediately to the others.

That s the view from 10,000 feet. The meat, however, is in the details. Here are some highlights to look for as you peruse the source code:

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

If NetDraw.cs looks familiar to you, that s because it s almost identical to Chapter 4 s NetDraw.cs. With a little help from .NET remoting, it didn t take much to turn a stand-alone application into a distributed application. You provide client and server components; the framework provides the plumbing that connects them. That s what .NET remoting is all about.

PaperServer.cs

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); } } [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 (); } }

Figure 15-6

Source code for a distributed drawing application.

Web.config

<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"/> </serverProviders> </channel> </channels> </application> </system.runtime.remoting> </configuration>

NetDraw.cs

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; Stroke CurrentStroke = null; 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 (); // Connect a handler to the object's NewStroke events NewStrokeHandler = new StrokeHandler (OnNewStroke); 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); 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); } CurrentStroke = null; } } protected override void OnKeyDown (KeyEventArgs e) { if (e.KeyCode == Keys.Delete) { // Delete all strokes and repaint lock (Strokes.SyncRoot) { Strokes.Clear (); } Invalidate (); } } protected override void OnClosing (CancelEventArgs e) { // Disconnect event handler before closing base.OnClosing (e); VirtualPaper.NewStroke -= NewStrokeHandler; } 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 ()); } }

NetDraw.exe.config

<configuration> <system.runtime.remoting> <application> <client> <wellknown type="Paper, PaperServer" url="http://localhost/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>



Programming Microsoft  .NET
Applied MicrosoftNET Framework Programming in Microsoft Visual BasicNET
ISBN: B000MUD834
EAN: N/A
Year: 2002
Pages: 101

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net