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:
- Message polling approach - HTTP Chat Sample (.NET) / HTTP Chat Sample (Delphi)
- Active server events - SuperTCP Chat Sample (.NET) / SuperTCP Chat Sample (Delphi)
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.
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.
RO.SDK related changes needed to send server events
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
RO.SDK related changes needed to receive server events
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).