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 must receive it. This article describes several approaches to the problem.
By events, I particularly mean a process satisfying the following statements:
- The caller sends the same message to several receivers.
- All calls are performed concurrently.
- The caller finally gets to know receivers� replies.
I hope the first statement does not require any additional explanation. All calls are performed concurrently. If connection to the specific client is slow (or is broken), sending to other clients will not be delayed until that specific client replies (or server recognizes 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 also, when it is possible.
Using the code (the first solution). .NET Native Events
Let's study the first sample. Well-known layer contains delegate declaration and public available event.
public delegate void MessageDeliveredEventHandler(string message);
public interface IChatRoom
{
void SendMessage(string message);
event MessageDeliveredEventHandler MessageDelivered;
}
In my implementation the caller calls SendMessage
method, which, in its turn, fires the event. This call can be made directly by a client though.
Clients create delegate instances pointed to MarshalByRefObject
-derived class and add handlers to the event. The only issue here is that delegate should point to well-known class, so as a work-around I declared well-known class that just calls 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);
iChatRoom.SendMessage(str);
Server provides an instance implementing IChatRoom
interface.
public void SendMessage(string message)
{
Console.WriteLine("\"{0}\" message will be sent to all clients.",
message);
if (this.MessageDelivered != null)
this.MessageDelivered(message);
}
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 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 OneWay
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 event�s 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 to my conditions. 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. Known layer contains event provider interface and client receiver interface:
public interface IChatClient
{
object ReceiveMessage(string message);
}
public interface IChatRoom
{
void SendMessage(string message);
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) {
IChatRoom iChatRoom = (IChatRoom) Activator.GetObject(typeof
(IChatRoom), "gtcp://127.0.0.1:8737/ChatRoom.rem");
iChatRoom.AttachClient(new ChatClient());
iChatRoom.SendMessage(str);
}
}
}
Server implements 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
{
static Hashtable _clients = new Hashtable();
public void AttachClient(IChatClient iChatClient)
{
if (iChatClient == null)
return ;
lock(_clients)
{
_clients[RemotingServices.GetObjectUri((MarshalByRefObject)
iChatClient)] = iChatClient;
}
}
public delegate object ReceiveMessageEventHandler(string 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)
{
IChatClient iChatClient = (IChatClient) entry.Value;
ReceiveMessageEventHandler remoteAsyncDelegate = new
ReceiveMessageEventHandler(iChatClient.ReceiveMessage);
AsyncCallBackData asyncCallBackData =
new AsyncCallBackData();
asyncCallBackData.RemoteAsyncDelegate =
remoteAsyncDelegate;
asyncCallBackData.MbrBeingCalled =
(MarshalByRefObject) iChatClient;
IAsyncResult RemAr = remoteAsyncDelegate.BeginInvoke
(message, asyncCallback, asyncCallBackData);
}
}
}
public static void OurAsyncCallbackHandler(IAsyncResult ar)
{
AsyncCallBackData asyncCallBackData = (AsyncCallBackData)
ar.AsyncState;
try
{
object result =
asyncCallBackData.RemoteAsyncDelegate.EndInvoke(ar);
}
catch(Exception ex)
{
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 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 definitely consider it if your clients do not hold on 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 at the server side and has absolutely different internal implementation.
Both known layer and client have absolutely the same implementation. We will find the difference only at the server.
Server constructs a Dispatcher
instance that will contain the list of recipients:
private static Dispatcher _dispatcher = new Dispatcher(typeof
(IChatClient));
private static IChatClient _caller;
To perform absolutely async processing, 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, server puts it into dispatcher
instance:
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:
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 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;
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);
}
}
}
You have all results gathered at one place, so you can make any decisions here.
Broadcast Engine has synchronous mode. In this mode all calls are made concurrently, but it waits while all clients reply or time out expires. Sometimes it�s very useful, but this mode consumes one thread until call will be finished. Take a look at Programming Guide which contains more details.
The pros:
- Easier to use than the second approach.
- All calls are made concurrently.
- Failed receiver will not affect other receivers.
- Broadcast Engine automatically recognizes situations when receiver is able to receive messages via true broadcast channel. If receivers did not receive a message via true broadcast channel, Broadcast Engine repeats sending of the message via usual channel. So you can utilize IP multicasting with minimum efforts.
- Broadcast Engine takes care of the sponsorship of attached MBR receivers.
The cons:
- You still have to declare well-known interface in order to implement events.