Click here to Skip to main content
15,879,326 members
Articles / Programming Languages / C#
Article

Persistent .NET Events in Stateless Remoting Server

Rate me:
Please Sign up or sign in to vote.
4.76/5 (18 votes)
5 Jan 20037 min read 93.7K   1K   43   14
This article demonstrates usage of .NET events in the stateless remoting server, thus allowing to use .NET event mechanism in highly available and scalable distributed applications.

Introduction

Remoting is a new and fascinating technology, and can be used to easily build distributed applications. I assume you already know how to use it, and know how to use remotable events. If you don't understand something in this article / demo application - read Ingo Rammer book (see below).

Sample application demonstrates useful patterns for deploying server metadata on clients and declaring shim classes for clients to be deployed on the server side. Some of the ideas were taken from the Ingo Rammer's book (Advanced .NET Remoting, ISBN 1-59059-025-2), www.dotnetremoting.cc.

If you want to create serious distributed applications, you should be concerned about fault tolerance and scalability of your servers. In general, remoting provides tools to create fully stateless and scalable applications (singlecall or stateless singleton server-activated objects).

Concept of event and multiple receivers is widely used in .NET, and even is supported by .NET remoting (although I think they could do better :) ).

Demo application

We'll look at simple client/server applications. Server has one event and one method, and raises event when method is called. Server will be hosted in command line application (can be hosted in IIS too), and will be stateless singleton. Client will be command line application too, and will subscribe to event, call method, and wait to "enter" before exiting. I won't discuss client in this article at all, because it is standard .NET client.

Using Demo Application.

If you run server, then client A, then client B – client A receives notification caused by the server method call made by B. Now you can exit client B and server, and execute them again. Leave client A running. When you execute client B, client A will get notification again – despite restarting of the server. So our server persists subscribed clients and calls them back based on the persisted data.

All code in this acticle is taken from the demo application, which includes all layers of the distributed application. Database used in this application can be re-created using included script – DB.sql. URLs and connection strings are read from configuration files, so you can customize it if you need. Demo application just has too much code to be presented in the body of the article, so I extracted fragments to highlight obscure points. :)

Problem description

Problems begin when you want to merge those two concepts – stateless remotable server and .NET events. Why? If your server is stateless, you (by the rule of thumb) can't have any instance or static variables in your server object, right? If you do have such variables, their values will be lost when server is restarted (so where is your fault tolerance), and they won't be shared between different instances of the server running on different hosts (so where is your scalability).

Solution is obvious – just store all those variables in the persistent storage and read it again each time when you handle client's request.

When you declare event, you declare delegate, and then declare event of the delegate's type.

C#
public delegate void BroadcastedMessageHandler(string text);
public event BroadcastedMessageHandler NewBroadcastedMessage;

So if you want to declare abstract server class with event, it will look like this

C#
public abstract class AbstractServer : MarshalByRefObject
{
    public abstract string Say(string what);
    public event BroadcastedMessageHandler NewBroadcastedMessage;
}

I hope you understand that declared class has one variable – NewBroadcastedMessage. Moreover, this variable is public, and will be freely modified by each += and -= statements in the client code. Even if we do know how to persist this variable – we need to know when it is changed first, right?

Intercepting subscribe / unsubscribe operations

Imagine this variable is of int type – what would we do ? Declare it as property. So the syntax of the abstract class will be:

C#
public abstract class AbstractServer : MarshalByRefObject
{
    public abstract string Say(string what);	
    public abstract event BroadcastedMessageHandler NewBroadcastedMessage;
}

Note abstract in the event declaration.

And implementation class :

C#
public class Server : AbstractServer
{
    public override event BroadcastedMessageHandler NewBroadcastedMessage
    {
        get
        {
        }
        set
        {
        }
    } 
}

But what will we return from get? And how will we distinguish between += and -= in set? Ok, it looks like this syntax isn't good for events. And folks in Microsoft thought the same way :) So they invented the property-like syntax for events:

C#
public class Server : AbstractServer
{
    public override event BroadcastedMessageHandler NewBroadcastedMessage
    {
        add
        {
        }
        remove
        {
        }
    }
}

Like with regular property, when you declare and implement it, no storage is generated and it is your responsibility to store changes and retrieve value when needed.

Tip: code of both add and remove methods can access passed parameter through value keyword, just like in set method of property. What you get in the value is the delegate passed by caller of the += or -=.

Remoted delegate persistence

What is remoted delegate? Let's look what's going on when client subscribes to event. Client creates new delegate and passes it to the server. Remoting de-serializes this delegate and passes it to our add method. We receive ObjRef of the delegate's target object (consider it network pointer) and serialize it. The result of the serialization can be stored like any other data:

C#
BinaryFormatter fmt = new BinaryFormatter();
ObjRef oRef = RemotingServices.GetObjRefForProxy
                ((MarshalByRefObject)value.Target);
MemoryStream ms = new MemoryStream(4096);
fmt.Serialize(ms,oRef);

Now we can call ms.GetBuffer() and receive serialized stream, which later can be safely stored somewhere. Actually we persist the remoted object, not the delegate itself. To persist delegate, we need to store its name too. In our sample, for each event we know, the name of the method which can be subscribed (it is always public method of the abstract class that defines event sink interface (shim class)), so we use that knowledge when we need to reconstruct the delegate.

And how do we reconstruct the delegate? Let’s assume we have DataSet and Table containing serialized ObjRef.

C#
MemoryStream ms = new MemoryStream(ds.tb_sinks[i].ObjRefStream,false);
object refObj = fmt.Deserialize(ms); 
ms.Close();

I'd bet you think if you serialized ObjRef, you will get ObjRef back when deserializing it? Nope. ObjRef is the network reference, so folks at Microsoft thought it is much more logical to automatically create transparent proxy to the remoted object which was referenced by serialized ObjRef. Do you think now you can use this transparent proxy ? Nope. When it is created , it does not initialize itself with type information, and will throw exception whenever you access something specific to your type. So? Just cast it back to the correct type.

C#
AbstractBroadcastedMessageEventSink sink = 
            (AbstractBroadcastedMessageEventSink)refObj;

Now you have transparent proxy of the remoted object. Given object and name of the method, you can create the delegate:

C#
BroadcastedMessageHandler d = 
                    (BroadcastedMessageHandler)Delegate.CreateDelegate(
                    typeof(BroadcastedMessageHandler), 
                    "BroadcastedMessageHandler");

//Now you can simple call the delegate:
d(m_Text);

Bells and whistles

You have all required information, so you can stop reading now :) Still, I decided to wrap this functionality and create some helper class. You can read the code in the attached applications. I separated code specific to each event type, and generic event persistence logic.

PersistentEventsHelper implements all functionality of adding, removing and calling all existing sinks. You need to pass to its methods also event name – any string, just be consistent to pass the same value for add, remove and invoke methods. This string helps to tell apart sinks of the different events – the same helper can work with different servers, and each server can have multiple events.

Calling clients in parallel

You should always check results when calling remoted object – else you can end up with thousands of objects in your storage, that causes exceptions every time you call them. You need to catch this exception, and remove the object that caused exception from the storage. Usually it is done just by calling delegate in the try/catch block.

The problem with this approach is that you will be making synchronous calls– i.e. if you need to call 50 sinks you will call second only after first returned. In addition, if your clients are GUI based, and client's code that accesses GUI elements from event handler does not marshal to GUI thread asynchronously, and event is delivered to the same client which caused its raising, your program will deadlock. (See PRB: GUI application hangs when using non-[OneWay]-Events FAQ for details). So I prefer to make all calls from the server to clients asynchronously. How? Simple by defining class, whose instance is responsible to check invocation result:

C#
InvocationResultChecker checker = new
        InvocationResultChecker(ds.tb_sinks[i].ID);
Caller.BeginInvoke((MarshalByRefObject)refObj,
        new AsyncCallback(checker.Callback),null);

Callback method calls EndInvoke and in case of any error, removes sink with the stored ID from persistent storage. Server defines class BroadcastMessageEventContext – one class for each event type. This class makes all castings and calls that are specific to event, so PersistentEventHelper is kept clean and generic.

C#
class BroadcastMessageEventContext
{
    string  m_Text;

    public BroadcastMessageEventContext(string text)
    {
        m_Text = text;
    }
    
    public void RemoteHandlerCaller(MarshalByRefObject TargetObject)
    {
        AbstractBroadcastedMessageEventSink sink = 
            (AbstractBroadcastedMessageEventSink)TargetObject;
        BroadcastedMessageHandler d = (BroadcastedMessageHandler)
                                    Delegate.CreateDelegate(
                                    typeof(BroadcastedMessageHandler),
                                    sink,
                                    //should be read from storage too, 
                                    //hardcoded here for simplicity
                                    "BroadcastedMessageHandler");
        d(m_Text);
    }
}

Conclusion

Now you know how to use .NET events in fault tolerant and scalable applications. Obviously this demo application isn't of production quality, and you should review code before using it in your real-world applications. Specially you should consider handling of remoting errors - current implementation forcefully unsubscribes the client, so it is possible for the client to run endlessly without receiving notifications and not knowing it. You should implement some keep-alive mechanisms when client will test if it is still subscribed.

You should also think about retry mechanism - in the WAN deployment it is possible to get short-time connectivity problems, so probably it is worth to try deliver notification in a second or two.

Enjoy ! :)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Architect mPrest Systems
Israel Israel
I am working with various Microsoft technologies since 1994 (remember Windows 3.1 ?Smile | :) ). Language of choice - C++/C#. I am mainly interested in software engineering and "generic solutions", so frequently I find myself designing / implementing frameworks and application skeletons, although sometimes I do various stuff from DB to Kernel Mode device drivers. I also love to understand why things are in the way they are, not just how to use them.
Currently I am Chief Software Architect and CTO of the software outsourcing company mPrest Ltd , Israel.

Comments and Discussions

 
GeneralExcellent Article Pin
aviad_e24-Jun-08 3:58
aviad_e24-Jun-08 3:58 
GeneralHTTP Keep-Alives Pin
coolspot1828-Sep-06 5:58
coolspot1828-Sep-06 5:58 
Generalcheck www.dotnetremoting.com Pin
Gbim3-Feb-05 23:29
sussGbim3-Feb-05 23:29 
GeneralRe: check www.dotnetremoting.com Pin
hulinning13-Jun-06 2:14
hulinning13-Jun-06 2:14 
GeneralEvents don´t work when a little time passed Pin
juanjoayuso18-Feb-04 6:18
juanjoayuso18-Feb-04 6:18 
GeneralRe: Events don´t work when a little time passed Pin
Alexander Arlievsky18-Feb-04 10:16
Alexander Arlievsky18-Feb-04 10:16 
GeneralProblems with demo. Pin
LanUx17-Feb-04 16:29
LanUx17-Feb-04 16:29 
GeneralRe: Problems with demo. Pin
Alexander Arlievsky18-Feb-04 10:02
Alexander Arlievsky18-Feb-04 10:02 
GeneralRe: Problems with demo. Pin
Shivonne1-Mar-04 22:48
Shivonne1-Mar-04 22:48 
GeneralRe: Problems with demo. Pin
Shivonne2-Mar-04 2:29
Shivonne2-Mar-04 2:29 
GeneralRe: Problems with demo. Pin
Alexander Arlievsky2-Mar-04 8:36
Alexander Arlievsky2-Mar-04 8:36 
GeneralPlease expand article! Pin
Daniel Cazzulino [XML MVP]31-Jan-03 4:47
Daniel Cazzulino [XML MVP]31-Jan-03 4:47 
GeneralDemo project cannot be downloaded Pin
iiacob6-Jan-03 9:12
iiacob6-Jan-03 9:12 
GeneralRe: Demo project cannot be downloaded Pin
Alexander Arlievsky6-Jan-03 22:18
Alexander Arlievsky6-Jan-03 22:18 
Fixed

==========================================================
Alexander Arlievsky
Chief Software Architect & CTO, mPrest Ltd
sasha@mprest.com
"The most valuable tools for debugging are brains"

==========================================================

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.