S60 5th Edition SDK
Example Applications Guide

Synchronous Client/Server Example

1. About this Example
2. Architecture
3. Design and Implementation


1. About this Example

This example demonstrates the architecture of a simple client server application utilising synchronous calls to the server. The server supplies the current time to its clients.


1.1 APIs demonstrated


1.2 Prerequisites

This example exists as a complete application, and has the standard Symbian OS application architecture employing the Application, Document, UI, and View classes. The example makes use of several other Symbian OS concepts which the reader should be aware of before attempting to understand this example. These are:


1.3 Running this example

The application initially presents a default time to the user, as shown in the following screenshot.

initial_screen.jpg

The Options menu presents the following choices:

options_menu.jpg

On selecting Display Time, the display will be updated, to reflect the server supplied time, as shown in the following screenshot.

updated_time.jpg


2. Architecture

This example exists as a complete application and has the standard Symbian OS application architecture employing the Application, Document, UI, and View classes. The reader should be familiar with this architecture before proceeding.


3. Design and Implementation

The following component diagram illustrates the split of classes over the Client and Server components, and their inter-relationships.

client-server_components.jpg

The client is a standard Symbian OS application, utilising an RSessionBase-derived object to communicate with the server. The server is implemented as an EXE.

In the following description, an overview of this example's design is presented in the Design Overview section. This is followed by a description of two use case scenarios, where the user:


3.1 Design overview

The Symbian OS client/server architecture uses four key concepts: server (CServer2), session (CSession2 and RSessionBase), sub-session (RSubSessionBase), and message (RMessage2, and RMessagePtr2).

Servers derive from CServer2, and are responsible for handling any requests from clients to establish connections.

The session represents the channel of communication between client and server. Clients use a derivation of RSessionBase, and servers use a derivation from CSession2.

The following class diagram shows the server-session associations for this example.

server-session_classes.png

A client can create multiple sessions with a server. It is more resource-efficient to use sub-sessions, but this feature is not employed in this example, for simplicity.

The RMessage2 class is the data structure passed between client and server. The client does not manipulate this directly; they use a TIpcArgs object to package the message information that is to be sent to the server. Server side sessions read from, and write to, this structure to receive and send data.


3.2 Creating a session

To create a session, the client must establish a channel of communication with the server. The following diagram shows the sequence involved.

session_sequence_diagram.png

  1. Connect is called on construction of the document. This function is responsible for starting the server, if necessary, and establishing communication with the server.
  2. It is necessary to start the server if it is not already running. StartServer accomplishes this, and returns an appropriate error code on completion. This is described further in the Starting the server section.
  3. If the server was started successfully (or was already running), a session is created by calling the inherited CreateSession function. This takes three parameters. These are:
  4. The CreateSession function calls corresponding function from kernel side. The actual name of this function is undocumented.
  5. The kernel layer calls the server's NewSessionL function. The exact mechanism is not documented but is included to show the complete sequence of events. NewSessionL verifies the client version, returning an error if it is not compatible with the server. It then creates a new CTimeServerSession to handle the client/server communication.
  6. The session is created using the standard Symbian OS two-phase construction.
  7. The new session is registered with the server, using IncrementSessions. The server keeps a count of the number of open sessions. The DecrementSessions function is called when a server session is destroyed, and terminates the server when there are no more open sessions.


3.2.1 Starting the server

A server needs its own thread of execution. In this example, the client uses StartServer to check whether the server is running. If the server is not running, a thread is created and the server is started.

The following diagram shows the sequence involved in creating the server.

start_server_sequence.png

  1. The StartServer function attempts to find an already running server. If it cannot, it will create a new server, waiting until the server has been created before returning.
  2. A new TFindServer object is created with the server name to search for.
  3. The call to TFindServer::Next returns KErrNone if the server was found.
  4. A new RSemaphore is created.
  5. The call to RSemaphore::CreateGlobal is passed the semaphore name to use. The semaphore needs to be global since the client and server run in separate processes (on the target). The server uses this semaphore to tell the client when the server construction is complete.
  6. CreateServerProcess will create and run the server. This is described in the Creating a process for the server.
  7. The client now waits for the server to initialise and become ready to accept requests. It accomplishes this by calling RSemaphore::Wait.
  8. When the server issues a corresponding RSemaphore::Signal, the client will continue. The semaphore is now closed using RSemaphore::Close.


3.2.2 Creating a process for the server

The sequence involved in creating a process for the server is shown below. On emulator it creates a thread, because server must run as a thread in the emulator's process, and a process in target device.

server_process_creating.png

  1. The CreateServerProcess function is responsible for creating a process for the server to run in, then starting the server.
  2. A handle for manipulating the server is created using RProcess::Create.
  3. The RProcess::Create is supplied with three parameters. These are:
  4. The process has now been created by the kernel. The process is suspended until a call to RProcess::Resume is made.
  5. The process is signalled to start by calling RProcess::Resume.
  6. Kernel's DProcess::Resume() is called.
  7. The E32Main is called, which is the EXE entry point.
  8. The E32Main call starts processing at the CTimeServer::ThreadFunction. This is described in the Server-side initialisation section.
  9. The handle to the server is no longer required, and is closed. Close must be called here, otherwise this process would have an open reference to the server's process. This would prevent the process being released after the server terminates.


3.2.3 Server-side initialisation

An EXE component implements the E32Main function, which provides the API for the operating system to start the executable. This function returns the address of the ThreadFunction function to be called.

The following diagram shows the initialisation the server undergoes on startup.

server_startup.png

  1. Kernel calls EXE entrypoint E32Main().
  2. CTimeServer::ThreadFunction is responsible for setting up any services that this thread/process requires, as well as creating an instance of the CTimeServer class to service requests from clients. It is important to note that ThreadFunction cannot leave. Any errors that occur need to be handled or must raise a panic, as there is no way to pass error information back to the calling thread.
  3. A cleanup stack is constructed and initialised. A thread/process does not have a cleanup stack by default, so one has to be created for it.
  4. CTimeServer::ThreadFunctionL is called within a TRAP to do the remainder of the construction that can leave. Following steps 5 -14 are executed inside of this function.
  5. An active scheduler is required, to handle active objects. It is created
  6. The active scheduler is installed.
  7. An anonymous instance of CTimeServer is created.
  8. As all the work of the server is activated via the active scheduler, it is not necessary to refer to server. The ConstructL calls CServer::StartL.
  9. The CServer::StartL call adds the server to the active scheduler and signals an active object request.
  10. Semaphore is created.
  11. Handle to the global semaphore is created.
  12. The global semaphore is signalled. This indicates that the server has now been constructed and is ready to use. The client will return from its RSemaphore::Wait the next time that it is scheduled by the run time system.
  13. Semaphore is closed.
  14. The active scheduler is started by calling CActiveScheduler::Start. This function only returns when CActiveScheduler::Stop() is called, which should occur when the final client is closed. Note that an active scheduler must have a pending active object before the call to CActiveScheduler::Start() is made. This was performed in steps 7 - 9.


3.3 Making a server request

The RMessage2 class is used to transfer information between the client and the server.

An instance of an RMessage2 object is automatically created for the session when the client calls the RSessionBase::SendReceive. This function can take a parameter that is a reference to TIpcArgs object, which can contain up to four 32-bit data arguments to be passed to server.

The sequence involved in making a server request is shown below.

server_request.png

  1. RequestTime is called by the application in response to the user selecting the Display Time option. A reference to a TTime object is passed into this function. A descriptor is now created, representing this TTime object. This descriptor's address is added into TIpcArgs object, which is passed to the inherited SendReceive function.
  2. The call to SendReceive results in the client thread being suspended.
  3. SendReceive calls undocumented function from kernel side.
  4. The kernel calls the ServiceL function of the associated server session and passes the appropriate RMessage2 object. This object specifies the operation to be performed by the server and also the message parameters. It also allows the server to indicate to the client when the request has completed, and raise a panic upon the client.
  5. ServiceL checks the requested service using RMessage2::Function.
  6. If requested service is ETimeServRequestTime it calls RequestTimeL, otherwise it raises a panic upon the client.
  7. RequestTimeL calculates the current time and creates a new descriptor representing it. RMessage2::WriteL is now called with the descriptor as parameter. The first parameter of the WriteL function is index value identifying the argument. This value must be 0 because the client's descriptor is the first argument. This function call causes the time to be written to the client's address space.


3.4 Capabilities

The application does not require any capabilities. The program capabilities are defined in both mmp-files as CAPABILITY NONE.

© Nokia 2009

Back to top