.NET Remoting - Events, Events? Events!

Introduction

You have the server and several clients. You want the server to fire an event and all of the clients or only some specific ones must receive it. This article describes several approaches to the problem.

By events, I particularly mean a process satisfying the following statements.

  1. The caller sends the same message to several receivers.
  2. All calls are performed concurrently.
  3. The caller finally gets to know the receiver's replies.

I hope the first statement does not require any additional explanation. All calls are performed concurrently. If the connection to the specific client is slow (or is broken), sending to other clients will not be delayed until that specific client replies (or the server recognizes the client's unavailability via time-out). The third statement stems from the real business needs. Usually, a caller has to know whether recipients successfully receive the message. Also, it would be good to gather recipients' replies, when it is possible.

Using the code (the first solution) .NET native events

Let's study the first sample. The well-known layer contains delegate declarations and publicly available events.

///  
/// Is called by the server when a message is sent.  
///  
public delegate void MessageDeliveredEventHandler(string message);  
///  
/// ChatRoom provides common methods for chatting.  
///  
public interface IChatRoom {  
  ///  
  /// Sends the message to all clients.  
  ///  
  /// Message to send.  
  void SendMessage(string message);  
  ///  
  /// Message delivered event.  
  ///  
  event MessageDeliveredEventHandler MessageDelivered;  
} 

In my implementation, the caller calls the SendMessage method, which, in turn, fires the event. This call can be made directly by a client though.

Clients create delegate instances pointed to the MarshalByRefObject-derived class and add handlers to the event. The only issue here is that the delegate should point to a well-known class, so as a workaround, I declared a well-known class that just calls the late-bound method (MessageReceiver class).

IChatRoom iChatRoom = (IChatRoom) Activator.GetObject(typeof(IChatRoom), "gtcp://127.0.0.1:8737/ChatRoom.rem");  
iChatRoom.MessageDelivered += new MessageDeliveredEventHandler(messageReceiver.MessageDelivered);  
// ... ask user to enter the message  
// and force the event  
iChatRoom.SendMessage(str);  
// Server provides an instance implementing IChatRoom interface.  
///  
/// Sends the message to all clients.  
///  
/// Message to send.  
public void SendMessage(string message) {  
  Console.WriteLine("\"{0}\" message will be sent to all clients.", message);  
  if (this.MessageDelivered != null)  
    this.MessageDelivered(message);  
}  
///  
/// Message delivered event.  
///  
public event MessageDeliveredEventHandler MessageDelivered; 

What we've got finally with this approach?

The pros

  • Very easy to implement if all business objects are located in the well-known layer.

The cons

  • Late-binding is required for business objects located in "unknown for clients" DLL.
  • Calls are made consecutively. The next client will be called only when the previous one returns a result.
  • If a client is unreachable or throws an exception, invoking is stopped and all remaining clients will not receive the message.
  • You should manage sponsorship separately. 

You can mark MessageReceiver.MessageDelivered method with one-way attribute to solve the second problem. But you should understand that there is no way to get call results in this case. Disconnected clients will never get excluded from the events recipient list. It's like a memory leak.

[OneWay]  
public void MessageDelivered(string message)  
{  
  if (this.MessageDeliveredHandler != null)  
    this.MessageDeliveredHandler(message);  
} 

Summary

This scheme is completely unacceptable. It is slow, unreliable, and does not fit my condition. You can use this scheme for short-living affairs that do not have too many clients and each client should have a possibility to break an event process.

Using the code (the second solution). Interface-based approach

Let's study the second sample. The known layer contains the event provider interface and client receiver interface.

///  
/// Describes a callback called when a message is received.  
///  
public interface IChatClient {  
  ///  
  /// Is called by the server when a message is accepted.  
  ///  
  /// A message.  
  object ReceiveMessage(string message);  
}  
///  
/// ChatRoom provides common methods for chatting.  
///  
public interface IChatRoom {  
  ///  
  /// Sends the message to all clients.  
  ///  
  /// Message to send.  
  void SendMessage(string message);  
  ///  
  /// Attaches a client.  
  ///  
  /// Receiver that will receive chat messages.  
  void AttachClient(IChatClient iChatClient);  
}  
IChatClient interface must be implemented by any object which wants to receive chat messages.Client class implements IChatClient interface.  
namespace Client {  
  class ChatClient: MarshalByRefObject, IChatClient {  
    static void Main(string[] args) {  
      // client attaches to the event  
      IChatRoom iChatRoom = (IChatRoom) Activator.GetObject(typeof(IChatRoom), "gtcp://127.0.0.1:8737/ChatRoom.rem");  
      iChatRoom.AttachClient(new ChatClient());  
      //... and asks user to enter a message.  
      // Then fires the event  
      iChatRoom.SendMessage(str);  
      //...  
    }  
  }  
} 

The server implements the IChatRoom interface and allows attaching the clients. I keep clients in the hash only because I want to remove failed receivers quickly. I added additional comments to the snippet below.

class ChatServer: MarshalByRefObject, IChatRoom {  
  ///  
  /// Contains entries of MBR uri => client MBR implementing IChatClient interface.  
  ///  
  static Hashtable _clients = new Hashtable();  
  ///  
  /// Attaches the client.  
  ///  
  /// Client to be attached.  
  public void AttachClient(IChatClient iChatClient) {  
    if (iChatClient == null)  
      return;  
    lock(_clients) {  
      //****************  
      // I just register this receiver under MBR uri. So I can find and perform an  
      // operation or remove it quickly at any time I will need it.  
      _clients[RemotingServices.GetObjectUri((MarshalByRefObject) iChatClient)] = iChatClient;  
    }  
  }  
  ///  
  /// To kick off the async call.  
  ///  
  public delegate object ReceiveMessageEventHandler(string message);  
  ///  
  /// Sends the message to all clients.  
  ///  
  /// Message to send.  
  /// Number of clients having received this  
  /// message.  
  public void SendMessage(string message) {  
    lock(_clients) {  
      Console.WriteLine("\"{0}\" message will be sent to all clients.", message);  
      AsyncCallback asyncCallback = new AsyncCallback(OurAsyncCallbackHandler);  
      foreach(DictionaryEntry entry in _clients) {  
        // get the next receiver  
        IChatClient iChatClient = (IChatClient) entry.Value;  
        ReceiveMessageEventHandler remoteAsyncDelegate = new ReceiveMessageEventHandler(iChatClient.ReceiveMessage);  
        // make up the cookies for the async callback  
        AsyncCallBackData asyncCallBackData = new AsyncCallBackData();  
        asyncCallBackData.RemoteAsyncDelegate = remoteAsyncDelegate;  
        asyncCallBackData.MbrBeingCalled = (MarshalByRefObject) iChatClient;  
        // and initiate the call  
        IAsyncResult RemAr = remoteAsyncDelegate.BeginInvoke(message, asyncCallback, asyncCallBackData);  
      }  
    }  
  }  
  // Called by .NET Remoting when an async call is finished.  
  public static void OurAsyncCallbackHandler(IAsyncResult ar) {  
    AsyncCallBackData asyncCallBackData = (AsyncCallBackData)  
    ar.AsyncState;  
    try {  
      object result = asyncCallBackData.RemoteAsyncDelegate.EndInvoke(ar);  
      // the call is successfully finished and  
      // we have call results here  
    } catch (Exception ex) {  
      // The call has failed.  
      // You can analyze an exception  
      // to understand the reason.  
      // I just exclude the failed receiver here.  
      Console.WriteLine("Client call failed: {0}.", ex.Message);  
      lock(_clients) {  
        _clients.Remove(RemotingServices.GetObjectUri(asyncCallBackData.MbrBeingCalled));  
      }  
    }  
  }  
} 

The pros

  • All calls are made concurrently.
  • Failed receivers do not affect other receivers.
  • You can conduct any policies about failed receivers.
  • You know the results of the calls and you can gather ref and out parameters.

The cons

  • Much more complicated than the first scenario. 

Summary

This is exactly a pattern you should use if you need to implement an event and you use native channels. I did not implement attaching and detaching sponsors here, but you should consider it if your clients do not hold on to receivers.

Using the code (the third solution). Broadcast Engine Let's do the same with Genuine Channels now. This approach looks like the previous one. But it is easier on the server side and has a different internal implementation.

Both known layer and client have absolutely the same implementation. We will find the difference only on the server.

The server constructs a Dispatcher instance that will contain a list of recipients.

private static Dispatcher _dispatcher = new Dispatcher(typeof(IChatClient));  
private static IChatClient _caller; 

To perform async processing, the server attaches a handler and switches on async mode.

static void Main(string[] args) {
    //...
    _dispatcher.BroadcastCallFinishedHandler += new BroadcastCallFinishedHandler(ChatServer.BroadcastCallFinishedHandler);
    _dispatcher.CallIsAsync = true;
    _caller = (IChatClient) _dispatcher.TransparentProxy;
    //...
}

Each time the client wants to receive messages, the server puts it into the dispatcher instance.

///  
/// Attaches the client.  
///  
/// Client to attach.  
public void AttachClient(IChatClient iChatClient) {  
  if (iChatClient == null)  
    return;  
  _dispatcher.Add((MarshalByRefObject) iChatClient);  
} 

When the server wants to fire an event, it just calls a method on the provided proxy. This call will be automatically sent to all registered receivers.

///  
/// Sends a message to all clients.  
///  
/// Message to send.  
/// Number of clients having received this message.  
public void SendMessage(string message) {  
  Console.WriteLine("\"{0}\" message will be sent to all clients.", message);  
  _caller.ReceiveMessage(message);  
} 

In my sample, I ignore call results. Anyway, the Dispatcher will automatically exclude failed receivers after the 4th failure by default. But if I would like to do it, I will write something like this.

public void BroadcastCallFinishedHandler(Dispatcher dispatcher, IMessage message, ResultCollector resultCollector) {  
  lock(resultCollector) {  
    foreach(DictionaryEntry entry in resultCollector.Successful) {  
      IMethodReturnMessage iMethodReturnMessage = (IMethodReturnMessage) entry.Value;  
      // here you get client responses  
      // including out and ref parameters  
      Console.WriteLine("Returned object = {0}", iMethodReturnMessage.ReturnValue.ToString());  
    }  
    foreach(DictionaryEntry entry in resultCollector.Failed) {  
      string mbrUri = (string) entry.Key;  
      Exception ex = null;  
      if (entry.Value is Exception)  
        ex = (Exception) entry.Value;  
      else  
        ex = ((IMethodReturnMessage) entry.Value).Exception;  
      MarshalByRefObject failedObject = dispatcher.FindObjectByUri(mbrUri);  
      Console.WriteLine("Receiver {0} has failed. Error: {1}", mbrUri, ex.Message);  
      // here you have failed MBR object (failedObject)  
      // and Exception (ex)  
    }  
  }  
} 

You have all the results gathered in one place, so you can make any decisions here.

Broadcast Engine has an asynchronous mode. In this mode, all calls are made concurrently, but it waits while all clients reply or time out expires. Sometimes its very useful, but this mode consumes one thread until call will be finished. Take a look at the Programming Guide which contains more details.

Summary

The pros

  • Easier to use than the second approach.
  • All calls are made concurrently.
  • Failed receivers will not affect other receivers.
  • Broadcast Engine automatically recognizes situations when the receiver can receive messages via a true broadcast channel. If receivers do not receive a message via the true broadcast channel, Broadcast Engine repeats the sending of the message via the usual channel. So you can utilize IP multicasting with minimum effort.
  • Broadcast Engine takes care of the sponsorship of attached MBR receivers.

The cons

You still have to declare a well-known interface to implement events.