Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Symbian OS Explained - Effective C++ Programming For Smartphones (2005) [eng].pdf
Скачиваний:
60
Добавлен:
16.08.2013
Размер:
2.62 Mб
Скачать

HOW IS A SERVER STARTED?

179

the basics of active objects). I’ll illustrate how to do this in the example code for a typical server in the next chapter. CServer::StartL() adds the server to the active scheduler and initiates the first message receive request.

For each CServer object created in the system, a corresponding DServer object is created in the kernel. Each DServer object holds a doubly-linked queue of all DSessions, representing all the currently open sessions for that server. It also owns a kernel object, DThread, which represents the server thread.

When implementing a server, you must create an active scheduler during server startup.

11.5How Do Synchronous and Asynchronous Requests Differ?

A client can request synchronous or asynchronous services8 from a server. Asynchronous requests take a TRequestStatus reference parameter, which is passed to RSessionBase::SendReceive(). This parameter is filled with a completion result by the server, via the kernel, when the request is completed by a call to RThread::RequestComplete(), which also signals the request semaphore of the client thread to notify it of request completion.

In fact, synchronous requests to the server are actually ”pseudosynchronous”. The synchronous overload of RSessionBase::SendReceive() declares a TRequestStatus object locally, passes this to the asynchronous overload of SendReceive() and then blocks the client thread until the request completes. In effect, the client thread is suspended and notified only when a server has completed its action, rather than continuing to poll the server for the status of a request. This is important on Symbian OS, to minimize power consumption.

11.6 How Is a Server Started?

There are several ways in which a server can be started and stopped:

System servers, e.g. the file server, are started by Symbian OS as part of OS startup because they are essential to the operating system.

8 For each asynchronous request function a server API provides, it must also provide a cancellation method.

180

THE CLIENT–SERVER FRAMEWORK IN THEORY

Application servers, which are only needed when certain applications are running, are started when clients need to connect to them. If an application attempts to start a server that is already running, say because it has been started by another application, no error results and only a single instance of the server runs. When the server has no outstanding clients, that is, when the last client session closes, it should terminate to save system resources. This type of server is known as a transient server. I’ll illustrate startup and shutdown for this kind of server in the next chapter.

Other servers, e.g. the POSIX server, are required by only a single application and are started with that application and closed when it terminates.

11.7How Many Connections Can a Client Have?

A client can have multiple ”connections” to a server through one or more sessions as follows:

Each connection can use a separate client–server session opened by a call to RSessionBase::CreateSession(). The sessions are independent of any other within the client thread, and each maintains its own context. That is, each client session has a corresponding CSharableSession object in the server and DSession object in the kernel. Use of multiple client sessions where they are not strictly necessary should be limited to reduce the number of kernel and server resources consumed – I’ll discuss this further later in this chapter.

The client may create a number of subsessions within a single session (the use of subsessions is described in Section 11.14). Client–server communication occurs via the owning session, using a unique handle to identify each individual subsession. The use of separate subsessions is more lightweight than separate sessions because it uses fewer kernel resources. However, they are more complex to implement server-side.

The server may support sharable sessions. Up to 255 threads in a client process may share a single session.

11.8What Happens When a Client Disconnects?

Typically, a class used to access a server has a termination method, which is usually called Close(). Internally, this method will call RHandleBase::Close(), which sends a disconnection message to the server and sets the session handle to zero. On receipt of this message,

HOW DOES CLIENT–SERVER COMMUNICATION USE THREADS?

181

the server ends its session with the client by destroying the associated CSharableSession-derived object (in addition, the kernel will destroy the DSession object which represents the session). If the client has any outstanding requests when Close() is called, they are not guaranteed to be completed.

11.9 What Happens If a Client Dies?

For a non-sharable session, if the client dies without calling Close(), the kernel sends a disconnection message to the server to allow it to cleanup sessions associated with that client thread. The kernel performs its thread-death cleanup by walking the queue of DSession objects and destroying any associated with the dead client thread.

If the session is sharable, the death of a single client thread does not close the session – the session is effectively process-relative by virtue of being sharable. To destroy the session, either the client process must terminate or the session must be closed explicitly on the client-side by a call to Close() on an RSessionBase handle.

11.10What Happens If a Server Dies?

If a server dies, the kernel will complete any waiting client requests with the error code KErrServerTerminated. This gives the client an opportunity to handle request failure and cleanup, destroying any RSessionBase objects open on the server. Even if the server is restarted, previous client sessions cannot be reused without first being reconnected to it, so the only valid operation is to call Close().

11.11How Does Client–Server Communication Use Threads?

A session between a client and a server is between one or more client threads and a separate server thread. Client code runs in user-mode threads. It submits requests to server code which also runs in user mode. The channel of communication between client and server is mediated by the kernel.

The Symbian OS server model is thread-based, allowing a server to run either in a separate process to the client, for greater isolation between client and server, or in the same process, to avoid the overhead of inter-process client–server communication.

182

THE CLIENT–SERVER FRAMEWORK IN THEORY

11.12What Are the Implications of Server-Side Active Objects?

The responsiveness of a server can be defined as the maximum time required to process a client message or the maximum time required to respond to an event on some device that it controls. The server uses non-pre-emptive active-object event-handling (described in Chapters 8 and 9). The response time is determined by the longest possible RunL() event-handler method of any active object running on the server thread, because an active object cannot be pre-empted when it is handling an event.

If a client makes a request while the server thread is already handling an event in a RunL() method, it runs to completion before the client request can be serviced. This is also true for external events occurring from resources owned by the server. Thus, if you want to write a highperformance server, there should be no long-running RunL() methods in any active objects in the server’s main thread.

This includes processing in the ServiceL() method of the CSharableSession-derived class, which is called by CServer::RunL(). This means that long-running operations must be performed by a separate thread or server.

Furthermore, the priority of a server thread should be chosen according to the maximum guaranteed response time, that is, the longest RunL() method of the server. You should not give a high priority to a server thread that performs lots of processing in its event handler, since it may block threads with more appropriately chosen, lower, priorities.

11.13What Are the Advantages of a Local (Same-Process) Server?

Local servers are useful when several related servers can run in the same process. For example, Symbian OS v7.0 runs the serial communications server, sockets server and telephony server in the same process (C32.exe). The servers are in a different process to their clients, so a context switch is still required, and resource integrity is maintained by the separation. However, interactions between the three servers occur in the same process and have a correspondingly lower overhead than they would otherwise (I’ll describe the overheads associated with using the client–server model in more detail shortly).

A private local server runs in the same process as its clients. It can be useful, for example, if you need to share client sessions to a server which does not support sharable sessions. The client process should use a private local server which does support sharable sessions and has a single open session with the non-sharable server. This private server services

WHAT ARE THE OVERHEADS OF CLIENT–SERVER COMMUNICATION? 183

requests from each of the client threads, passing them through as requests to its single session with the non-sharable server.

11.14What Are the Overheads of Client–Server Communication?

Session Overhead

Although a client can have multiple sessions with a server, each session consumes limited resources in both the server and the kernel. For each open client session, the kernel creates and stores a DSession object and the server creates an object of a CSharableSession-derived class. This means that each connecting session may give rise to a significant speed overhead. Rather than creating and opening multiple sessions on demand, client code should aim to minimize the number of sessions used. This may involve sharing a session, or, for servers which do not support this, passing the open session between functions or defining classes that store and reuse a single open session.

For efficiency, where multiple sessions are required, a client–server implementation may provide a subsession class to reduce the expense of multiple open sessions. To use a subsession, a client must open a session with the server as normal, and this can then be used to create subsessions which consume fewer resources and can be created more quickly. This is done using the RSubSessionBase class, the definition of which is shown below (from e32std.h):

class RSubSessionBase

{

public:

inline TInt SubSessionHandle(); protected:

inline RSubSessionBase(); inline RSessionBase& Session();

IMPORT_C TInt CreateSubSession(RSessionBase& aSession,

TInt aFunction,const TAny* aPtr);

IMPORT_C void CloseSubSession(TInt aFunction);

IMPORT_C TInt Send(TInt aFunction,const TAny* aPtr) const;

IMPORT_C void SendReceive(TInt aFunction,const TAny* aPtr,

TRequestStatus& aStatus) const;

IMPORT_C TInt SendReceive(TInt aFunction,const TAny* aPtr) const; private:

RSessionBase iSession;

TInt iSubSessionHandle; };

A typical client subsession implementation derives from RSubSessionBase in a similar manner to a client session, which derives from RSessionBase. The deriving class provides simple wrapper functions to hide the details of the subsession. To open a subsession, the

184

THE CLIENT–SERVER FRAMEWORK IN THEORY

derived class should provide an appropriate wrapper function (e.g.

Open()) which calls RSubSessionBase::CreateSubSession(), passing in an existing RSessionBase-derived session object. CreateSubSession() also takes an integer ”opcode” to identify the ”create subsession” request, and a pointer to an array of pointers (which may be used to pass any parameters required to service the request across the client–server boundary).

Once the subsession has been created, RSubSessionBase::SendReceive() and Send() methods can be called, by analogy with those in RSessionBase, but only three parameters of the request data array may be used because the subsession class uses the last element of the data array to identify the subsession to the server.

On the server side, the code to manage client–server subsessions can be quite complex. It usually requires reference counting to manage subsessions over the lifetime of the session, and typically uses the CObject-derived classes. You can find more information about these, somewhat confusing, classes in your SDK documentation.

A good example of the use of subsessions is RFile, which derives from RSubSessionBase and is a subsession of an RFs client session to the file server. An RFile object represents a subsession for access to individual files. I’ll illustrate the use of RFs and RFile later in this chapter, but you should consult your SDK for further information about the use of the Symbian OS filesystem APIs.

It’s worth noting that connections to the file server can take a significant amount of time to set up (up to 75 ms). Rather than creating multiple sessions on demand, RFs sessions should be passed between functions where possible, or stored and reused.

Each client–server session has an associated overhead in the kernel and server. Client code should minimize the number of sessions it uses, for example by sharing a session, or by defining classes that store and reuse a single open session. A server may also implement subsessions to be used as lightweight alternatives to multiple open sessions.

Performance Overhead

You should be aware of the system performance implications when using the client–server model. The amount of data transferred between the client and server does not cause so much of an overhead as the frequency with which the communication occurs.

The main overhead arises because a thread context switch is necessary to pass a message from the client thread to the server thread and back

WHAT ARE THE OVERHEADS OF CLIENT–SERVER COMMUNICATION? 185

again. If, in addition, the client and server threads are running in different processes, a process context switch is also involved.

A context switch between threads stores the state of the running thread, overwriting it with the previous state of the replacing thread. If the client and server threads are in the same process, the thread context switch stores the processor registers for the threads. If the client and server are running in two separate processes, in addition to the thread context, the process context (the address space accessible to the thread), must be stored and restored. The MMU must remap the memory chunks for each process, and on some hardware this means that the cache must be flushed. The exact nature of the overhead of a thread or process context switch depends on the hardware in question.

Inter-thread data transfer between threads running in separate processes can also have an overhead because an area of data belonging to the client must be mapped into the server’s address space.

How Can I Improve Performance?

For performance reasons, when transferring data between the client and server, it is preferable, where possible, to transfer a large amount of data in a single transaction rather than to perform a number of server accesses. However, this must still be balanced against the memory cost associated with storing and managing large blocks of request data.

For example, Symbian OS components that frequently transfer data to or from the filesystem generally do not use direct filesystem access methods such as RFile::Read()or RFile::Write(). Instead, they tend to use the stream store or relational database APIs, which you can find described in the system documentation. These higher-level components have been optimized to access the file server efficiently. When storing data to file, they buffer it on the client side and pass it to the file server in one block, rather than passing individual chunks of data as it is received.

Thus, taking the stream store for example, RWriteStream uses a client-side buffer to hold the data it is passed, and only accesses the file server to write it to file when the buffer is full or if the owner of the stream calls CommitL(). Likewise, RReadStream pre-fills a buffer from the source file when it is created. When the stream owner wishes to access data from the file, the stream uses this buffer to retrieve the portions of data required, rather than calling the file server to access the file.

When writing code which uses a server, it is always worth considering how to make your server access most efficient. Take the file server, for example: while there are functions to acquire individual directory entries in the filesystem, it is often more efficient to read an entire set of entries and scan them client-side, rather than call across the process boundary to the file server multiple times to iterate through a set of directory entries.