How to implement a simple server interface

Provides code snippets to help you to implement a simple server interface.

Handling asynchronous requests

The implementation of a server requires a class derived from CServer2. This is the active object base class responsible for handling the asynchronous requests from the client program.

Construction and initialisation

An instance of the CServer2 derived class is, typically, created by the server's thread function. As an active object, it needs a priority and this is passed as a parameter to the constructor. The choice of priority value depends on the server's design. If the server can, ultimately, have more than one active object, then it may be important for the CServer2 active object to have the highest priority.

The server can now be started. This is a code fragment taken from the example that can be found at ...\examples\Base\IPC\ClientServer\complex.

CCountServServer *pS=new CCountServServer(EPriorityStandard);
__ASSERT_ALWAYS(pS!=NULL,CCountServServer::PanicServer(ESvrCreateServer));
...
      
    // Start the server
TInt err = pS->Start(KCountServerName);
if (err != KErrNone)
    {
    CCountServServer::PanicServer(ESvrStartServer);
    }

The function CServer2::Start() adds the CServer2 active object to the active scheduler and issues the first request for messages. The server is now waiting for messages.

As with all active objects, the completion of requests for messages is handled by the CServer2::RunL() protected member function.

Handling requests

A request for a connection by a client thread results in the creation of a new session. The request for a connection results in a call by the client/server framework to the CServer2::NewSessionL() function. A derived class must provide an implementation - creating and initialising an instance of a CSession2 derived class. The framework takes this newly created session object to the server's queue.

For a non sharable session, requests for disconnection by a client thread cause the relevant CSession2 object to be deleted. The CSession2 destructor should perform appropriate cleanup.

Any other message is passed to CSession2::ServiceL(). This function must be implemented by a derived class.

Server side session representation

The base class CSession2 represents a client's session on the server side. This class provides the standard session behaviour. A class derived from CSession2 must be defined and implemented. The following class definition, taken from the example that can be found at: ...\examples\Base\IPC\ClientServer\simple, is typical:

class CCountServSession : public CSession2
    {
public:
    CCountServSession();

      //service request
    void ServiceL(const RMessage2& aMessage);
    void DispatchMessageL(const RMessage2& aMessage);

      // services available to initialize/increase/decrease/reset and
      // return the counter value.
    void SetFromStringL(const RMessage2& aMessage);
    void Increase();
    void Decrease();
    void IncreaseBy(const RMessage2& aMessage);
    void DecreaseBy(const RMessage2& aMessage);
    void CounterValue(const RMessage2& aMessage);
    void Reset();

protected:
        // panic the client
   void PanicClient(const RMessage2& aMessage,TInt aPanic) const;
        
private:
    TInt iCount;
    };

Note the following:

  • The function ServiceL() is called by the client/server framework to handle all messages except requests to connect and disconnect.

  • ServiceL() calls DispatchMessageL() under a trap harness.

  • DispatchMessageL() determines the appropriate message service function to call by examining the operation code of the current message. This is simply a mechanism to delegate the handling of different request types.

  • The class provides message service functions: Increase(), IncreaseBy() etc. to service specific messages from clients.

  • The function SetFromStringL() needs a string specified by the client and therefore needs to read data from the client address space.

ServiceL()

This is implemented as follows:

void CCountServSession::ServiceL(const RMessage2& aMessage)
    {
    TRAPD(err,DispatchMessageL(aMessage));
    aMessage.Complete(err);
    }

After calling the appropriate service function via DispatchMessageL(), the asynchronous request is completed with aMessage.Complete() which passes the completion code back to the client.

DispatchMessageL()

This is implemented as follows:

void CCountServSession::DispatchMessageL(const RMessage2& aMessage)
    {
    switch (aMessage.Function())
        {
    case ECountServSetFromString:
        SetFromStringL(aMessage);
        return;
    case ECountServIncrease:
        Increase();
        return;
    case ECountServIncreaseBy:
        IncreaseBy(aMessage);
        return;
        ...
    case ECountServValue:
        CounterValue(aMessage);
        return;
    ...
    default:
        PanicClient(aMessage,EBadRequest);
        return;
        }
    }

IncreaseBy()

This message service function is implemented as follows:

void CCountServSession::IncreaseBy(const RMessage2& aMessage)
    {
    iCount = iCount + aMessage.Int0(); 
    }

Note that we need to pass the message object to the function. The Int0() function is used to get the integer specified in the client call - the '0' on the end of the function name indicates that the integer is the first parameter in the set passed across from the client.

SetFromStringL()

This message service function is implemented as follows:

void CCountServSession::SetFromStringL(const RMessage2& aMessage)
    {
         // length of passed descriptor (1st parameter passed from client)
    TInt deslen = aMessage.GetDesLength(0);
    
      // Passed data will be saved in this descriptor.
    RBuf buffer;
      
      // Max length set to the value of "deslen", but current length is zero
    buffer.CreateL(deslen);
      
      // Do the right cleanup if anything subsequently goes wrong
    buffer.CleanupClosePushL();
    
      // Copy the client's descriptor data into our buffer.
    aMessage.ReadL(0,buffer,0);
    
      // Now do a validation to make sure that the string only has digits
    if (buffer.Length() == 0)
        {
        User::Leave(ENonNumericString);
        }
    ...
      // Do rest of work to convert from 
      // string to integer, and assign.

RMessage::ReadL() reads the descriptor from the client address space as specified by the first argument in the message, and copies the data into the descriptor specified as its second argument. A basic test is done to make sure there data is supplied.

CounterValue()

This is implemented as follows:

void CCountServSession::CounterValue(const RMessage2& aMessage)
    {
    TPckgBuf<TInt> p(iCount);
    aMessage.WriteL(0,p);
    }

It writes data back to a descriptor in the client address space. The corresponding client request is:

TInt RCountServSession::CounterValue()
    {
    TInt res=0;
    TckgBuf<TInt> pckg;
    
      // Note that TPckgBuf is of type TDes8
    TIpcArgs args(&pckg);
    SendReceive(ECountServValue, args);
    
      // Extract the value returned from the server. 
    res = pckg();
    return res;
    }

Notes

  • The TInt is packaged into a descriptor before being passed to the server. The packaging mechanism is known as package buffer.

  • The write operation copies the descriptor, i.e. the package buffer containing the integer value, back to the descriptor in the client address space. Note that the zero specified in aMessage.WriteL(0,p); means that the argument referred to is the first in the list passed across from the client side via the TIpcArgs object.