Event Sinks and Server Callbacks

Event Sinks and Server Callbacks Overview

In most scenarios of multi-tier applications the client takes the active role of sending requests to the server, while the server passively waits for requests, processes them and sends back a response.

Sometimes, you will find the need to have the server actively contacting one of more clients to notify them of specific events. For example, a database application might notify a client about changes to specific records made by a different user, or a chat system might forward messages sent by one client to a group of other clients logged onto the same chat room.

Remoting SDK provides the concept of Event Sinks to achieve this functionality.

Event Sinks

Principally, you can consider an Event Sink to be a service interface that will be implemented by the client, instead of the server. When designing your service structure in Service Builder, event sinks will look similar to services - they are a collection of methods grouped together in an interface.

Since events are uni-directional (meaning the server can invoke them on the client, but the clients will not send a response), event methods can only contain parameters that will be sent into the event - they do not provide a result value or any outgoing return parameters.

But besides this restriction, events can make use of all the functionality of a service definition, including the use of all the types supported by Remoting SDK as parameters.

Implementing Event Handlers

Just like services, every event sink defined in your RODL will be represented as an interface in the _Intf source file. To implement an event handler for a particular event sink on the client, all you need to do is implement the interface and provide implementation bodies for all the event methods it defines.

The class that implements it can be any class in your project - it could be your main form or a class you specifically define just to handle the events.

Event subscription in Remoting SDK is handled on a per-sink level - a client can subscribe to receive all events in a particular event sink, or none of them, it cannot subscribe to a subset only. But, of course, the client is free to ignore certain events by simply providing an empty implementation for the respective event method.

Receiving Events on the Client

Remoting SDK includes the EventReceiver (.NET) or TROEventReceiver (Delphi) components that handle the retrieval and dispatching of events. Enabling a client to receive events consists of 4 simple steps: * Drop an event receiver component onto your client application * Assign its Channel property to the channel you wish to use to communicate with the server * Assign its Message property to the message you use to communicate with the server (note that since events are dispatched based on the ClientID, this must be the same Message component you also use for your regular communication) * Register your event handler (the object implementing the event interface) by calling the RegisterEventHandler (.NET) or RegisterEventHandlers (Delphi) method. Notice: In the .NET compatible mode (described below) registering the event handler on the client side is not enough, the client must be registered to receive events on the server side.

Cocoa

ROEventReceiver implements all is needed for receiving events on client. Here is simple example of code:

//a good practice use distinct channel for event sinks, no need create distinct message instance
callbackChannel = [ROClientChannel channelWithTargetURL:url];
//creation of new instance
eventReceiver = [[ROEventReceiver alloc] initWithChannel:callbackChannel message:message];
//setup necessary parameters
eventReceiver.serviceName = @"NewService";
eventReceiver.minimumPollInterval = 1;
eventReceiver.maximumPollIntrval = 3;
[eventReceiver registerEventHandler:self];
eventReceiver.active = YES;

Events handler is delegate which implements event's protocol. This can be any class which implements event sink's interface. In simplest case it is application delegate:

@interface roAppDelegate : NSObject <NSApplicationDelegate, INewEventSink>
</source>
It should implement event sink methods and assigned to "delegate" property for :
<source lang="objc">
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification 
{
    // Insert code here to initialize your application
    roServerAccess.sharedInstance.eventDelegate = self;
}

- (void)EventSinkCallback:(NSString*)someStringParam{
    [eventLabel setStringValue:someStringParam];
}

Sending Events on the Server (.NET)

In Remoting SDK for .NET, the Service.GetEventSink method allows you to obtain an event proxy object for any event sink defined in your RODL simply by passing the type of the required interface, and casting the result, such as

 IChatEvents ev = GetEventSink(typeof(IChatEvents)) as IChatEvents;

This will return a proxy object on which you can call any of the event methods - which will then automatically be dispatched to all clients registered to receive the events. GetEventSink also provides different overloads that allow you to specify a restricted list of recipient IDs, or provide a IEventTargets instance for more detailed control.

Sending Events on the Server (Delphi)

In Remoting SDK for Delphi, event repositories (descendants of the TROEventRepository class) are used to store and dispatch event calls. To obtain an event proxy for any particular event sink, simply cast your service's EventRepository to the desired event sink writer interface that is defined in your _Intf unit, such as:

 ev := (EventRepository as IChatEvents_Writer);

In addition to the actual event methods, this event writer interface contains additional properties inherited from the IROEventWriter base interface that you can use to control the recipients of the events in detail.

Deciding which Clients will Receive Events

How it is decided which clients will actually be signed up for a particular event sink may depend on the platform used. In the .NET version, the server implementation has explicit control over clients event subscription via the SubscribeClientEventSink and UnsubscribeClientEventSink methods of the Service class. The first method must be called for each client session to let the client receive events.

Since Spring 2010 version in the Delphi edition there are two modes of operation: .NET-compatible and Legacy. Besides some internal implementation details the main difference in these modes consists of which side is controlling the subsciption. In the legacy mode the subscription is controlled by the client itself via remote methods call. But this is a security flaw: it allows to create a malicious client application that will flood the server with millions of subscriptions causing the denial of service and the server has no ability to resist it. That's why it is strongly recommended to cease using the legacy mode. Events mode is controlled with two properties named LegacyEvents, in the TROEventReceiver and TRORemoteDataModule classes.

In the legacy mode the event receiver will automatically perform a call to the server to register the client to receive the registered event sink(s), when you call RegisterEventHandlers.

In the .NET-compatible mode each session must be explicitly subscribed via the RegisterEventClient call. The client can be unsubscribed via the UnRegisterEventClient. For example (based on the File Broadcast Sample (Delphi), the FileBroadcastService_Impl.pas file):

procedure TFileBroadcastService.uploadfinished(const filename: Unicodestring; const filesize: Int64);
begin
  // This line should be added so the event can reach the client:
  RegisterEventClient(GuidToString(ClientID), EID_FileEvents); 
  (EventRepository as IFileEvents_Writer).OnNewFileAvailable(Session.SessionID, filename, filesize);
end;

Polling vs. Active Events

Depending on the Channel type you are using, events can either be actively delivered to the client, or stored on the server and retrieved via polling. At this time, only the Super TCP Channel supports active event delivery, for all other types, the event receiver will poll the server at regular intervals.

This difference is handled internally in the channel and event receiver components, your application code doesn't need to distinguish between the two modes of retrieving events.

Cross-platform Compatibility

Events implementations are cross-platform compatible when using active events. When using channels that require polling the Delphi application (client or server) should operate in non-legacy mode, i.e. LegacyEvents properies are set to false and the client is explicitly subscribed by the server. The Cocoa events implementation class, ROEventReceiver, also operates in .NET-compatible mode.

Load balancing

Scenario of servicing a huge amount of clients also depends on type of client channel. Super Channels maintain persistent connection to the server, so one server instance can work with rather limited number of clients. With Simple Channels it is possible to work with much bigger number of clients but event delivering is not such fast and polling produces unwanted load on the server. Using load balancing and Super Channels gives possibility to combine fast event dispatching and possibility seamless scale server side. Most session managers intended to work with single server instance or require manual setup a shared database. In this case Olympia Server can be used in such server farm to share client session data. Now Olympia is run as single, dedicated server instance but it design give possibility to implement in future versions collaboration of multiple Olympia instances. Such feature can be used without any changes once it become available.

See Also