MultiCast Events

Note: you can switch .NET/Delphi platform with switch at the right side (below):

The problem

In some cases, when developing distributed applications, some way is needed to allow the server application to send messages to the client applications. For example, when developing a chat application you'll need some mechanism to allow the server to tell its clients that they should reconnect or than a new user has entered the chat. Usually this can be done by implementing some kind of message queue and client application that periodically calls the server to check if new messages are available. But proper implementation of this can be quite a pain, because you'll need to develop not only a reliable message queue that has to remember the queue position for every client (because some clients might already receive some messages while others not), but some kind of periodical call mechanism for client application. This system would be a rather complex and hard to test properly.

Fortunately, RemObjects SDK not only perfectly solves the problem of distributed communication, even between Win32 and .NET applications (including ones running on Mono in Linux or MacOS), it also greatly decreases development time and adds flexibility in solving the problems described above.

The solution

To solve the problem of communication between the server and its clients we introduced a new feature called Multicast Server Events.

Using this feature, you can implement server callbacks to the client application with a minimum of effort, while both server and client can be implemented using Delphi or one of the .NET languages. Note that depending on the projected server workload and performance needed you can use two different approaches - message polling by client application or a server that actively calls its clients. In the first case you can manage how often clients poll the server (i.e. depending on the client's priority), while in the second case your clients will receive messages with minimum delay. So these two approaches allow you to select what is more important to you - to minimize server workload while serving multiple clients or to have a robust communication from server to clients. Also note, that active server events can be implemented only by using our newly introduced RO.SDK Super Channels (described in another article), while message polling could utilize any channel type provided by RO.SDK

Steps needed to implement both these approaches will be described below.

If you want to see a Multicast Server Event in action, you should take a look at these Remoting SDK samples:

How to use RO Multicast Events

In this topic we will describe steps needed to implement RO MulticastEvents. In cases where the two different approaches mentioned above need different implementation it will be mentioned.

Event definition - server side

Open RODL and add an Event Sink named ServerMessageEventSink using the context menu or toolbar. Add a new event named ServerMessageEvent to ServerMessageEventSink with one Widestring parameter named Message. Note that for obvious reasons events cannot return anything and its parameters can only be in type. Also, you need to add a method named Subscribe to the service declaration, with a Boolean parameter named aSubscribe. Later we will use this method to manage clients' subscriptions to server events.

RODL with server event definition opened in RemObjects Service Builder

Code changes at server side

After you close Service Builder, code files will be automatically generated.


In .NET, not only the usual _Intf, _Impl and _Invk files will be generated, but also an _Events file.

The _Events file contains a declaration of an ancestor of RemObjects.SDK.Server.EventSinkProxy, used as proxy to represent remote clients in the server application:

public class ServerMessageEventSink_EventSinkProxy : RemObjects.SDK.Server.EventSinkProxy, IServerMessageEventSink

The _Intf file contains not only the ordinary service interface, proxy and asynchronous proxy declaration, but also the declaration of an interface that should be implemented by events receivers and declaration of an invoker class for those events.

[RemObjects.SDK.EventSink(Name = "ServerMessageEventSink", InvokerClass = typeof(ServerMessageEventSink_EventSinkInvoker))]
public interface IServerMessageEventSink : RemObjects.SDK.IROEventSink
{
     void ServerMessageEvent(string Message);
}

public class ServerMessageEventSink_EventSinkInvoker : RemObjects.SDK.EventSinkInvoker
{
    public static void Invoke_ServerMessageEvent(RemObjects.SDK.IROEventSinkHandlers @__Handlers, RemObjects.SDK.IMessage @__Message)
    {
        string Message = @__Message.ReadWideString("Message");
        for (int @__i = 0; (@__i < @__Handlers.Count); @__i = (@__i + 1))
        {
            ((IServerMessageEventSink)(@__Handlers[@__i])).ServerMessageEvent(Message);
        }
    }
}


In Delphi, only usual sets of files will be generated. Two additional interfaces will be added to the _Intf file:

{ IServerMessageEventSink }
IServerMessageEventSink = interface
  ['{41026DC0-4D65-4FCC-85D4-128962E1D8F6}']
  procedure ServerMessageEvent(const Message: Widestring);
end;

{ IServerMessageEventSink_Writer }
IServerMessageEventSink_Writer = interface(IROEventWriter)
  ['{41026DC0-4D65-4FCC-85D4-128962E1D8F6}']
  procedure ServerMessageEvent(const __Sender : TGUID; const Message: Widestring);
end;

_Impl and _Invk files generated will remain unchanged.

Managing subscriptions

We will implement a simple and obvious way to manage clients subscriptions in the Subscribe method:

public void Subscribe(Boolean aSubscribe)
{
    if (aSubscribe)
        this.SubscribeClientEventSink(typeof(IServerMessageEventSink));
    else
        this.UnsubscribeClientEventSink(typeof(IServerMessageEventSink));
}
procedure TServerMessageService.Subscribe(const aSubscribe: Boolean);
begin
  if aSubscribe then
    self.EventRepository.AddSession(Session.SessionID)
  else
    self.EventRepository.RemoveSession(Session.SessionID);
end;

Now, the client can call Subscribe(true) to subsribe to server events and Subscribe(false) to unsubsribe.


You should add these components to your server's main form: MemoryMessageQueueManager and EventSinkManager. Also, you need to set the Message property of the EventSinkManager to an already existing message component.

The MemoryMessageQueueManager component will be used to handle message queuing. This implementation of Message Queue Manager stores the message queue in memory.

The EventSinkManager component will handle the process of sending events to the clients.


You should add these components to your server's main form: ROInMemoryEventRepository and ROEventSessionManager. Also, you need to set the Message property of the ROInMemoryEventRepository to an already existing message component. Property SessionManager should be set to the newly added ROEventSessionManager component.

You should also add a main form unit to the uses section of your server's _Impl file. After this you should set the service's properties EventRepository and SessionManager to corresponding main form components.

Raising server events

We will add raising of a server event to the implementation of the Sum method:

public virtual int Sum(int A, int B)
{
    ((IServerMessageEventSink)GetEventSink(typeof(IServerMessageEventSink)))
        .ServerMessageEvent("Sum was called");
    return A+B;
}
function TServerMessageService.Sum(const A: Integer; const B: Integer): Integer;
var
  ev : IServerMessageEventSink_Writer;
begin
  ev := (self.EventRepository as IServerMessageEventSink_Writer);
  ev.ExcludeSender := False;
  ev.ServerMessageEvent(Session.SessionID, 'Sum was called');

  Result := A + B;
end;
function TServerMessageService.Sum(const A: Integer; const B: Integer): Integer;
var
  ev : IROEventWriter<IServerMessageEventSink>;
begin
  ev := Self.EventRepository.GetWriter<IServerMessageEventSink>(Session.SessionID);
  ev.ExcludeSender := False;
  ev.Event.ServerMessageEvent('Sum was called');

  Result := A + B;
end;

As you can see, this is pretty simple to do.

Defining MulticastEvents on Client side

 

 
If you are planning to actively receive server events and are using one of the Super Channels, you should add a EventReceiver component to the client's main form and set its Channel and Message properties to already existing main form components.

If you are using one of the non-Super channels, you should add another channel component of the same type to the main form and point the EventReceiver's Channel property to the newly added channel component. Also you can setup polling-related properties like MaximumMessagesPerPoll, MaximumPollInterval and MimimumPollInterval. This is the only difference between the implementation of active server events and the message pooling approach.

 
The mechanism that is used for server events delivery is selected automatically based on the channel type.

You should add the ROEventReceiver component to the client main form and point its Channel and Message properties to corresponding, already existing components. You also need to properly set the ServiceName property. It should be named after the service that will send service events.

Also, you can setup the message polling interval using ROEventReceiver's Interval property.

Note that only the asynchronous SuperChannel allows the server to actively communicate with the client, so active server events can only be implemented when SuperHttp or SuperTcp channels are used.

Using synchronous channels, you can only implement the message polling mechanism, because there is no way the server could send a message to the client without prior established connection from client to the server.

Subscribing to server events

To manage event subscriptions, you can use the already defined Subscribe method. In this sample we will subscribe to events in the OnLoad event handler and unsubscribe in the FormClosing event handler:

private void MainForm_Load(object sender, EventArgs e)
{
    fService.Subscribe(true);
    this.eventReceiver1.RegisterEventHandler(this, typeof(IServerMessageEventSink));
}

private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
    this.eventReceiver1.UnregisterEventHandler(this);
    fService.Subscribe(false);
}
procedure TClientForm.FormActivate(Sender: TObject);
begin
  self.fService := self.RORemoteService as IServerMessageService;

  self.ROEventReceiver1.RegisterEventHandlers([EID_ServerMessageEventSink], [ self ]);
  self.ROEventReceiver1.Active := true;
  self.fService.Subscribe(true);
end;

procedure TClientForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  self.fService.Subscribe(false);
  self.ROEventReceiver1.Active := false;
  self.ROEventReceiver1.UnregisterEventHandlers([EID_ServerMessageEventSink]);
end;

Note that fService is declared earlier as

private IServerMessageService fService;
var fService: IServerMessageService;

and initialised in form constructor using

service = CoServerMessageService.Create(this.message, this.clientChannel);
self.fService := self.RORemoteService as IServerMessageService;

eventReceiver1 is the name of the EventReceiver component added at step 2.2.1.

Implementing server event handlers

To receive server events we should implement IServerMessageEventSink declared in the service's _Intf file. We will use a very simple indication of the received service event:

void IServerMessageEventSink.ServerMessageEvent(String Message)
{
    MessageBox.Show(Message);
}
procedure TClientForm.ServerMessageEvent(const Message: Widestring);
begin
  MessageDlg(Message, mtError, [mbOK], 0);
end;

Triggering server events

As you can remember, the server event we declared is fired when the service's Sum operation is called. So we will add a call of this operation in our client application. To do this, we will add a button to the client's main form and implement its OnClick event handler:

private void button1_Click(object sender, EventArgs e)
{
    fService.Sum(0, 0);
}
procedure TClientForm.Button1Click(Sender: TObject);
begin
  self.fService.Sum(0, 0);
end;

RO MulticastEvents in action

Even this simple sample demonstrates MulticastEvents in action.

When you start the server and multiple clients, each of them will show a message box when you press the inlaying button. You can also see, that event subscriptions are lost when the server is restarted, while you still can call service functions. Depending on the approach used (message polling or active events), you will see a message box immediately after pressing the button calling the service's method or after some polling interval.

Note that much more sophisticated and advanced samples can be ouind in the RemObjects SDK Samples folder, as mentioned in part 1.2 of this article.

Summary

RO MulticastEvents described in this article allow you to implement server applications with minimum overhead that can actively send messages to its clients. Two different approaches described allow you to select between the message polling approach that allows you to minimize server workload, and the active server event approach that allows you to minimize event retrieval delay. Using the RemObjects SDK framework, you can easily implement this feature, even if your communications need to cross platform boundaries (from .NET to Win32 and from Windows to Linux, MacOS an even iPhone).