[Previous] [Contents] [Next] [IONA Technologies]


Chapter 14
Callbacks from Servers to Clients


Contents

14.1 Implementing Callbacks in OrbixWeb
14.1.1 Defining the IDL Interfaces
14.1.2 Writing a Client
14.1.3 Writing a Server
14.2 Avoiding Deadlock in a Callback Model
14.2.1 Using Non-blocking Operation Invocations
14.2.2 Using Multiple Threads of Execution
14.3 An Example Application of Callbacks
14.3.1 The IDL Specification
14.3.2 The Client Application
14.3.3 The Central Server Application



Usually, OrbixWeb clients invoke operations on objects in OrbixWeb servers. However, OrbixWeb clients can implement some of the functionality associated with servers, and all servers can act as clients. This flexibility increases the range of client-server architectures which can be implemented with OrbixWeb.

Callback invocations are one application programming technique which takes advantage of this OrbixWeb characteristic. A callback is an operation invocation made from a server to an object which is implemented in a client. Such invocations allow servers to send information to clients without forcing clients to explicitly request the information.

In this chapter, we will describe a common approach to implementing callbacks in an OrbixWeb application and we will illustrate this description with a small example.

14.1 Implementing Callbacks in OrbixWeb

We will introduce a simple model for implementing callbacks in a distributed system by describing the following steps:

14.1.1 Defining the IDL Interfaces

In this system, clients will invoke operations on servers and servers will invoke operations on clients. Consequently, our IDL definitions must define the interfaces through which each type of application can access the other. In the simplest case, this involves two interfaces, for example:

Our model will be based on the fact that the client application will supply an implementation of type ClientOps, while the server will implement ServerOps.

It is important to note that clients are not registered in the OrbixWeb Implementation Repository and therefore the server in this example will not be able to bind to the client's implementation object. Instead, our IDL definition supplies an operation which allows the client to explicitly pass an implementation object reference to the server. For example, our IDL might look like this:

Of course, this is a rather contrived and simplistic definition; later in this chapter we will describe a more realistic example and describe the factors which must be considered when modifying this definition.

14.1.2 Writing a Client

The first step in writing a client is to implement the interface for the client objects, in this case interface ClientOps. This can be done using either the TIE or BOAImpl approach, as if the client were an OrbixWeb server. In our example, we will assume that the implementation is called ClientOpsImplementation.

The client main() method can be outlined as follows:

The client creates an implementation object of type ClientOpsImplementation. It then binds to an object of type ServerOps in the server. At this point, the client holds an implementation object for type ClientOps and a proxy for an object of type ServerOps, as shown in Figure 14.1.

Figure 14.1: Client objects

In order to allow the server to invoke operations on the ClientOps implementation object, the client must pass this object reference to the server. Consequently, the client now calls the operation sendObjRef() on the ServerOps proxy object, as shown in Figure 14.2.


Figure 4.2: Client passes implementation object reference to server

Finally, the client must prepare to receive incoming operation invocations from the server. Unlike a server application, the client should not call the method impl_is_ready() on the _CORBA.Orbix object. Instead, the client should use an event processing method which does not implicitly initialise the application server name. The client can safely call either the method processEvents() or the method processNextEvent() on the _CORBA.Orbix object.

Note that these event processing methods are defined on OrbixWeb class BOA (in package IE.Iona.Orbix2.CORBA) and the _CORBA.Orbix object in the client must be initialised as type BOA (as described in Chapter 3, "OrbixWeb Configuration" of the OrbixWeb Reference Guide) if that client is to receive callbacks.

The client call to processEvents() will block while waiting for incoming OrbixWeb events. If the server invokes an operation on the ClientOps object reference forwarded by the client, this invocation will be processed by processEvents() and routed to the correct method in the client's implementation object.

14.1.3 Writing a Server

The server application can be coded as a normal OrbixWeb server. In particular, an implementation class for interface ServerOps should be defined, one or more implementation objects should be created, and the method impl_is_ready() should be called on the _CORBA.Orbix object.

The implementation of the method sendObjRef() for interface ServerOps requires special attention. This method receives an object reference from the client. When this object reference enters the server address space, a proxy for the client's ClientOps object is created. It is this proxy which the server will use to call back to the client, and the implementation of sendObjRef() should store the reference to the proxy for later use.

For example, our implementation of interface ServerOps might look like this:

Subsequent to the creation of the proxy in the server address space, the server may invoke the operation callBackToClient(). For example, the server might initiate this call in response to an incoming event or after impl_is_ready() returns. The method invocation on the ClientOps proxy is routed to the client implementation object as shown in Figure 14.3.

Figure 14.3: Client passes implementation object reference to server

The transmission of requests from server to client is possible because OrbixWeb maintains an open communications channel between client and server while both processes remain alive. The callback invocation can be sent directly to the client and does not need to be routed through an OrbixWeb daemon. Therefore, the client can process the callback event without being registered in the OrbixWeb Implementation Repository and without being given a server name.

14.2 Avoiding Deadlock in a Callback Model

By default, when an application invokes an IDL operation on an OrbixWeb object, the caller is blocked until the operation has returned. In a system where several applications have the potential to both invoke and implement operations, deadlocks may arise.

For example, in the application described above a simple deadlock may arise if the server attempts to call back to the client in the implementation of the method sendObjRef(). In this case, the client would be blocked on the call to sendObjRef() when the server invokes callBackToClient(). The callBackToClient() call would block the server until the client reached an event processing call (in this case processEvents()) and handled the server request. Each application would be blocked, pending the return of the other, as shown in Figure 14.4.


Figure 14.4: Deadlock in a simple callback model

Unfortunately, it is not always possible to design a callback architecture in which simultaneous invocations between groups of processes are guaranteed never to occur. However, there are alternative methods to avoid deadlock in an OrbixWeb system. The two primary approaches are:


We will discuss each approach in turn in the following subsections.

14.2.1 Using Non-blocking Operation Invocations

There are two ways to invoke an IDL operation in an OrbixWeb application without blocking the caller: the first is to declare the operation as oneway in the IDL definition; the second is to invoke the operation using the "deferred synchronous" approach supported by the OrbixWeb Dynamic Invocation Interface (DII).

An IDL operation may be declared as oneway only if it has no return value, out, or inout parameters. A oneway operation can only raise an exception if a local error occurs before an invocation is transmitted. Consequently, the delivery semantics for a oneway request are "best-effort" only; that is, a caller can invoke a oneway request and continue processing immediately, but will not be guaranteed that the request will arrive at the server.

Deadlock could have been avoided in the model described in section 14.2 by declaring either sendObjRef() or callBackToClient() as a oneway operation, for example:

In this case, the client's call to sendObjRef() would return immediately, without waiting for the server's implementation method call to return. This would allow the client to enter the OrbixWeb event processing call, processEvents(). At this point, the callback invocation from the server would be processed and routed to the client's implementation of callBackToClient(). When that method invocation returns, the server would no longer block and both applications would again wait for incoming events.

A similar functionality can be achieved by using the OrbixWeb DII deferred synchronous approach to invoking operations. As described in Chapter 19, "Dynamic Invocation Interface", the DII allows an application to dynamically construct a method invocation at runtime, by creating a Request object (type Request is defined in package IE.Iona.Orbix2.CORBA). The invocation can then be sent to the target object using one of a set of methods supported by the DII.

Section 19.5 describes how the methods ORB.send_deferred(), ORB.send_oneway(), ORB.send_multiple_requests_deferred(), and ORB.send_multiple_requests_oneway() can be called on the _CORBA.Orbix object to invoke an operation without blocking the caller. If any of these methods is used, the caller can continue to process in parallel with the target implementation method. Operation results can be retrieved at a later point in the caller's processing and deadlock can be avoided as if the operation call was a oneway invocation.

14.2.2 Using Multiple Threads of Execution

An OrbixWeb application may create multiple threads of execution. In order to avoid deadlock, it may be useful to create a thread which is dedicated to handling OrbixWeb events. For example, an OrbixWeb application might instantiate an object of the following class:

Invoking run() on an object of this type will start the execution of a thread which simply processes incoming OrbixWeb events.

If another thread in this application becomes blocked while invoking an operation on a remote object, the event processing will continue in parallel. So, in our example, the remote operation may safely call back to the multi-threaded application without causing deadlock.

In the next section we will describe an example application of callbacks which uses this approach to processing OrbixWeb events in callback clients.

14.3 An Example Application of Callbacks

The example we will describe in this section is based on a distributed "chat group" application, the source code of which is available in the OrbixWeb webchat demonstration directory. In this application, users join a chat group by downloading an OrbixWeb callback-enabled client. Using this client, the user can send text messages to a central server. The server then forwards these messages to other clients which have joined the same group.

The client provides an interface which allows each user to select a current chat group, to view messages sent to that group and to send messages to other group members. For example, if user "chris" runs the client, this user is added to the group "General" by default and initially the client interface appears as shown in Figure 14.5.


Figure 14.5: WebChat client interface

The option menu labelled "Groups:" allows the user to select a chat group. The user receives all messages sent to the current group and can only join one group at any given time.

The main text area displays all messages sent to the current group. These messages include messages from other group members and system messages which indicate that other members have joined or left the group.

Finally, a text field and "Send" button allow users to send messages to the group.

The central server simply manages all messages sent to all chat groups. It receives the messages from client applications and forwards these messages to other clients appropriately. As such, the server does not require any direct user interaction and could run without a user interface.

However, in this example, we provide a server monitor interface which displays statistical information about the messages in the system. This interface is shown in Figure 14.6.


Figure 14.6: WebChat server interface

The interface includes information about the number of users, the members of each group, the total number of messages sent through the system and the total number of messages sent to each group. A "Message Peek" option also allows each message sent through the system to be viewed. This information is available because all messages are routed through this central server.

14.3.1 The IDL Specification

The IDL specification for this application includes two interface definitions: a CallBack interface which will be implemented by clients and a Chat interface which will be implemented by the server. The source code for this IDL is as follows:

Each client will implement a single CallBack object. This object will allow the client to receive notification from the server when new messages are sent to the client's current chat group.

The server will implement a set of Chat objects; one object for each available chat group. A client will invoke the operation registerClient() on a Chat object to join the chat group which that object supports. Similarly, a client application will call removeClient() to leave a chat group. A client which has been registered with a chat group will call the operation sendMessage() to send a text message to other members of the same group.

14.3.2 The Client Application

The source code for the client application comprises three Java classes:


The class CallBackImplementation allows a server to forward a chat message to a client. The implementation of operation newMessage() displays the incoming message in the main text area of the client user interface:

The main() method and the constructor of class ClientGUI implement the initial flow of control for the client application, and the code for this class can be outlined as follows:

The static main() method begins by retrieving command line arguments and then instantiates an object of type ClientGUI. The constructor for type ClientGUI initialises the client interface by calling the method initDisplay(), details of which will not be described here. It then invokes three methods to initialise the OrbixWeb functionality of the client: these methods are initOrbixWebObjects(), registerClient() and startEventThread().

Method initOrbixWebObjects() instantiates a single implementation object of type CallBackImplementation and binds to a server object which implements IDL interface Chat:

Method registerClient() invokes operation registerClient() on the server Chat object, passing the client's CallBackImplementation object reference as a parameter:

Method startEventThread() uses an EventProcessor object to create a thread in which incoming OrbixWeb events will be processed, including server callback invocations:

The definition of class EventProcessor is exactly as described in section 14.2.2.

When the client's initialisation is complete, it enters the Java event processing loop and responds to user interface events through the method handleEvent() and a set of subsidiary methods. Each of these methods handles an event for a specific user interface element as follows:

14.3.3 The Central Server Application

The server application maintains a single Chat implementation object for each chat group. Each Chat implementation object stores a list of CallBack proxy objects, where each proxy is associated with a single client. In this way, each server object is aware of every client which has joined that object's chat group, and can forward incoming chat messages to those group members.

The main functionality of the server is implemented in three Java classes:


The class ChatImplementation allows a client to register with a server object which implements a chat group. The source code for this class is as follows:

An object of this class maintains an ObjectCacheEntry object as a member variable. This variable represents the head of a linked list of CallBack proxy objects, where each object is associated with a client which has joined the current chat group. The linked list is initially empty.

A client joins the ChatImplementation object's chat group by calling registerClient(). The implementation of this operation adds the client's CallBack object reference to the linked list. A client leaves a chat group by calling removeClient(), the implementation of which removes the client's CallBack object reference from the linked list.

The operation sendMessage() allows a client to send a text message to all clients in the same chat group. The implementation of this operation accepts the message as a string parameter. It then cycles through the linked list of client object references, making a callback operation invocation on each, with the string value as a parameter. In this way, the server object redistributes text messages to all clients in a chat group.

The class ObjectCacheEntry, is a simple linked list node structure which stores an object reference value. The source code for this is as follows:

The class ServerGUI implements the flow control for the server application. The source code for this class can is outlined below:

The server main() method first instantiates an object of type ServerGUI. The constructor for this object initialises the server display and invokes the method initGroupObjects() to create a set of ChatImplementation objects. Each ChatImplementation object implements a single chat group, where the group name is implemented as the object marker.

When the ServerGUI object has been created and the server implementation objects are available, the server main() method invokes impl_is_ready() on the _CORBA.Orbix object and awaits incoming requests from clients.



[Roadmap] [Introduction] [GS: Applications] [GS: Applets]
[IDL] [Mapping] [Programming OrbixWeb] [Publishing Objects] [Retrieving Objects] [IIOP]
[Running Clients] [Running Servers] [Exceptions] [Inheritance] [Callbacks] [Contexts]
[API Configuration] [TypeCode] [Any] [DII] [IR] [Filters] [Smart Proxies] [Loaders] [Locators]
[Index]