UnderGround Forums
 

ITGround >> Tutorial: .NET Remoting


6/19/05 6:10 PM
Ignore | Quote | Vote Down | Vote Up
theseanster
252 The total sum of your votes up and votes down Send Private Message Add Comment To Profile

Edited: 19-Jun-05
Member Since: 05/13/2002
Posts: 7491
 

Remoting and Callbacks

For those of you who don't know what .NET remoting is, it is basically like DCOM with a lot more flexibility built in. Unlike the Win32 world, when a process spins up for managed code there is something called an AppDomain which is sort of like a process within a process. A Win32 process can contain 1 or more AppDomains. Remoting essentially lets two components from different AppDomains talk to each other. This is regardless of whether each component is on the same physical machine or across the network. The following example code will show you how to create a remote object using a binary format over TCP. This is the fastest. For demo purposes, I'm hosting each application in a console. Typically you need some kind of host (or "daemon") like a Windows Service or IIS to host a remoting object. To use IIS, however, you have to run over HTTP instead of TCP (SOAP over HTTP). The nice thing is that IIS gives you the easy of hosting along with its security features, whereas if you implement binary over TCP, you are probably going to host the remoting server object in a Windows Service memory space and have to manually secure it by rolling your own security even sinks or using something like IPSec.

There are actually two examples going on here...one is remoting,  the other is the fact that the remoting call is done asynchronously to avoid blocking the client. Hopefully this doesn't overcomplicate the example, but you should be able to distinguish the two concepts fairly easily. In order for a remoted server object to perform a callback to the client, they have to reside on the same network. So, I went for kind of a "wow factor" but keep in mind that if your clients are behind a firewall, this callback example is essentially worthless. Although I would question why you are using remoting over a firewall instead of using ASMX, but that's another topic.

So, the approach I usually take with remoting is to create a common assembly that contains the "contracts" for the functionality that the server performs. I hate Soapsuds.exe, and I hate when people directly reference the server to get the metadata because quite frankly you never know if you are remoting or loading the server directly into your own AppDomain! So, shared interfaces and/or base classes is the best IMHO, so that's the route I'm going.

The Shared Assembly: TaskManagement.dll

The shared assembly defines an interface for the operation that the remote server object will adhere to. This assembly is simply a class library. You basically see an event being defined and a method that takes no parameters. Additionally you see a "shim" for the callback. This shim is necessary to avoid the requirement of the server needing to have the metadata for each client in order to make the callback. That is, our client needs to know where the server resides to make the call, but...if we create a shim to manage the remoting of delegates, the server only needs to know about the shim, not the client. The shim knows about the client and forwards the communications to the client. Notice that you will be seing MarshalByRefObject...all remoted objects must inherit from this class.

6/19/05 6:12 PM
Ignore | Quote | Vote Down | Vote Up
theseanster
252 The total sum of your votes up and votes down Send Private Message Add Comment To Profile

Edited: 19-Jun-05 06:12 PM
Member Since: 05/13/2002
Posts: 7492

using System;

 

namespace TaskManagement

{

      public delegate void JobFinishedDelegate(long confirmationNumber);

 

      public interface IJob

      {          

        void Start();

        event JobFinishedDelegate JobFinished;

      }

 

    public class CallBackShim : MarshalByRefObject

    {

        private JobFinishedDelegate target;

 

        private CallBackShim(JobFinishedDelegate target)

        {

            this.target += target;

        }

 

        public void JobFinishedShim(long confirmationNumber)

        {

            target(confirmationNumber);

        }

 

        public static JobFinishedDelegate Create(JobFinishedDelegate target)

        {

            CallBackShim shim = new CallBackShim(target);

            return new JobFinishedDelegate(shim.JobFinishedShim);

        }

    }

}

6/19/05 6:12 PM
Ignore | Quote | Vote Down | Vote Up
theseanster
252 The total sum of your votes up and votes down Send Private Message Add Comment To Profile

Edited: 19-Jun-05 06:13 PM
Member Since: 05/13/2002
Posts: 7493

The Server code

The Server is basically just a console application that fires up an instance of a class the conforms to the interface defined in the shared library. Notice the configuring remoting services to host the objects on a particual TCP port. Pick any you like. There are a few unusual tricks going on in the Console app code, main the TypeFilterLevel.Full being set in a BinaryServerFormatterSinkProvider object. This is basically a security override to allow me to pass a delegate through AppDomains. If not, it could be a security vulnerability because a delegate encapsulates a method call and some bad guy could put in some other method that conforms to the delegate's definition. Other than that, I'm just registering a TCP channel and registering the object type as being available to remote as a Singleton. A singleton is a design pattern where you get only one instance of a particular object that is shared by everyone who calls it. Another type of server-activated remoting object is SingleCall, which means you have a stateless object - you get a new instance on every method call so having a conversation with one of those is rather difficult. Singleton allows us to do callbacks.

The server class LongRunningJob will asynchronously fire off a job by calling it's own private delegate using BeginInvoke. It basically simulates a long running job for 10 seconds, calls back to the client and cleans up the delegate gue using EndInvoke.

 

6/19/05 6:19 PM
Ignore | Quote | Vote Down | Vote Up
theseanster
252 The total sum of your votes up and votes down Send Private Message Add Comment To Profile

Edited: 19-Jun-05
Member Since: 05/13/2002
Posts: 7494

using System;

using System.Collections;

using System.Runtime.Remoting;

using System.Runtime.Remoting.Channels;

using System.Runtime.Remoting.Channels.Tcp;

using System.Runtime.Serialization.Formatters;

using System.Threading;

using TaskManagement;

 

namespace RemotingServer

{

    public class LongRunningJob : MarshalByRefObject, IJob

    {

        public event JobFinishedDelegate JobFinished;

 

        private delegate void RunJobDelegate();

 

        private void RunJob()

        {

            //simulate long running task for 10 seconds

            Thread.Sleep(10000);

        }

 

        public void Start()

        {

            AsyncCallback cb = new AsyncCallback(this.CallBack);

            RunJobDelegate del = new RunJobDelegate(this.RunJob);

            del.BeginInvoke(cb, del);

        }

 

        private void CallBack(IAsyncResult ar)

        {

            if (null != JobFinished)

            {

                try

                {

                    JobFinished(DateTime.Now.Ticks);

                }

                catch (Exception e)

                {

                    Console.WriteLine("Failed to fire event.");

                    Console.WriteLine(e.Message);

                    throw e;

                }

            }

 

            if (ar != null && ar.AsyncState != null && ar.AsyncState is RunJobDelegate)

            {

                ((RunJobDelegate) ar.AsyncState).EndInvoke(ar);

            }

        }

    }

  

6/19/05 6:20 PM
Ignore | Quote | Vote Down | Vote Up
theseanster
252 The total sum of your votes up and votes down Send Private Message Add Comment To Profile

Edited: 19-Jun-05
Member Since: 05/13/2002
Posts: 7495

public class Server

    {

        [STAThread]

        private static void Main(string[] args)

        {

            Hashtable options = new Hashtable(1);

            options.Add("port", 8084);

            BinaryServerFormatterSinkProvider provider = new BinaryServerFormatterSinkProvider();

            provider.TypeFilterLevel = TypeFilterLevel.Full;

            TcpChannel channel = new TcpChannel(options, null, provider);

 

            ChannelServices.RegisterChannel(channel);

            RemotingConfiguration.RegisterWellKnownServiceType(typeof (LongRunningJob),

                "LongRunningJob.rem",

                WellKnownObjectMode.Singleton);

 

            Console.WriteLine("Press ENTER to quit");

            Console.ReadLine();

        }

    }

}

6/19/05 6:20 PM
Ignore | Quote | Vote Down | Vote Up
theseanster
252 The total sum of your votes up and votes down Send Private Message Add Comment To Profile

Edited: 19-Jun-05
Member Since: 05/13/2002
Posts: 7496

The Client Code

The client application is probably the easiest to read. Typically you don't even put all of the remoting configuration crap in the code. Instead you put it all in a config file and then simply call a single RemotingConfiguration method to load it all. In this case I am registering channel zero just for the callback, and then late binding to an remoting object hosted on the server via its TCP port using Activator.GetObject. I call a factory method (hey wow, another design pattern) to get back a delegate to subscribe to the server event for a callback. Then I fire the Start method and wait. That's it!

 

6/19/05 6:21 PM
Ignore | Quote | Vote Down | Vote Up
theseanster
252 The total sum of your votes up and votes down Send Private Message Add Comment To Profile

Edited: 19-Jun-05
Member Since: 05/13/2002
Posts: 7497

using System;

using System.Runtime.Remoting.Channels;

using System.Runtime.Remoting.Channels.Tcp;

using TaskManagement;

 

namespace RemotingClient

{

    public class ClientApp

    {

        [STAThread]

        private static void Main(string[] args)

        {

            TcpChannel channel = new TcpChannel(0);

            ChannelServices.RegisterChannel(channel);

 

            IJob job = (IJob) Activator.GetObject(typeof (IJob),

                "tcp://localhost:8084/LongRunningJob.rem");

 

            JobFinishedDelegate cb = CallBackShim.Create(new JobFinishedDelegate(job_OnJobFinished));

            job.JobFinished += cb;

            job.Start();

 

            Console.WriteLine("Please wait...");

            Console.ReadLine();

        }

 

        private static void job_OnJobFinished(long confirmationNumber)

        {

            Console.WriteLine("Job finished. Confirmation {0}", confirmationNumber);

            Console.WriteLine("Please ENTER to exit");

        }

    }

}

6/26/05 12:14 AM
Ignore | Quote | Vote Down | Vote Up
theseanster
252 The total sum of your votes up and votes down Send Private Message Add Comment To Profile

Edited: 26-Jun-05
Member Since: 05/13/2002
Posts: 7575
Guess this one was a snoozer, huh? :-)
6/26/05 2:04 PM
Ignore | Quote | Vote Down | Vote Up
deepu
22 The total sum of your votes up and votes down Send Private Message Add Comment To Profile

Edited: 26-Jun-05
Member Since: 01/01/2001
Posts: 8181
This is awesome! Thank you.
6/26/05 2:18 PM
Ignore | Quote | Vote Down | Vote Up
theseanster
252 The total sum of your votes up and votes down Send Private Message Add Comment To Profile

Edited: 26-Jun-05
Member Since: 05/13/2002
Posts: 7579
Can you write me a VB app that would seperate my animal porn from my scat porn? I thought rob was writing you that animal porn app to separate by kingdom, phylum, class, order, genus, species? * can't believe I remember that list from grade school *
6/26/05 3:48 PM
Ignore | Quote | Vote Down | Vote Up
Andrew Yao
Send Private Message Add Comment To Profile

Edited: 26-Jun-05
Member Since: 01/01/2001
Posts: 2829
family goes between order and genus.
6/26/05 4:05 PM
Ignore | Quote | Vote Down | Vote Up
theseanster
252 The total sum of your votes up and votes down Send Private Message Add Comment To Profile

Edited: 26-Jun-05
Member Since: 05/13/2002
Posts: 7589
Dammit, now we have to tell Rob to rewrite his sorting algorithm!
1/27/06 9:24 PM
Ignore | Quote | Vote Down | Vote Up
theseanster
252 The total sum of your votes up and votes down Send Private Message Add Comment To Profile

Edited: 27-Jan-06
Member Since: 05/13/2002
Posts: 9518
TTT by request...
1/28/06 12:13 AM
Ignore | Quote | Vote Down | Vote Up
Revolver of Reason
Send Private Message Add Comment To Profile

Edited: 28-Jan-06
Member Since: 01/01/2001
Posts: 29678
I keeel you ded. D-E-D DED!
1/28/06 12:20 AM
Ignore | Quote | Vote Down | Vote Up
theseanster
252 The total sum of your votes up and votes down Send Private Message Add Comment To Profile

Edited: 28-Jan-06
Member Since: 05/13/2002
Posts: 9532
I just like the word "shim". Sounds all techy and stuff
11/13/12 11:42 AM
Ignore | Quote | Vote Down | Vote Up
theseanster
252 The total sum of your votes up and votes down Send Private Message Add Comment To Profile

Member Since: 5/13/02
Posts: 21204

bump

this is more advanced, and from .NET 1.0. I will update this to use WCF using the 4.0 or 4.5 if anyone is interested


Reply Post

You must log in to post a reply. Click here to login.