|
||
The client-server framework is an important part of Symbian OS. There is necessarily a level of complexity in implementing a server for your application(s), but we provide client interface code and server code that you can copy and paste into your own implementations.
We classify Symbian OS servers into three basic types:
Transient servers, i.e. servers that are started on demand when a client has need of the server, and exit after the last client has disconnected - sometimes after a short delay. For example, the socket server etc.
System servers, i.e. servers that are started as part of the system boot process. For example, the kernel, the file server, the window server etc.
Local servers, i.e. servers that are "local" to a process to provide multi-threaded access to thread-specific resources. For example, the posix server (in the C Standard Library), the AWT server (in Java), etc.
Transient servers are normally the most difficult to code because they introduce the most complex startup and shutdown situations. However, they are in common use, and by providing you with working code, we ensure that:
you handle startup and shutdown situations correctly.
client code and server code follow a common pattern.
The transient server supplied here runs in a thread in its own process. This gives the server independence from its clients. This is important because if the client were to create the server as a separate thread within its own process, then the server would exit, i.e. terminate, if its owning process terminated. This would be true even if clients in other processes were still connected to it.
Equally as important:
a client does not explicitly need to start the server prior to connecting; this is done by the client interface.
the server terminates when not in use shortly after the last client disconnects.
the code handles concurrency issues, the most common of which is handling the case where the server is closing down or exiting at the same time that another client is trying to connect.
There are four main parts to the code we supply:
the client interface - this is a .DLL
that is used by clients -
you can use this as it is, and build on it.
the server -
this is a .EXE
that runs in its own process - you can use this as
it is, and build on it.
test code - this corresponds to the client, and uses the client interface to communicate with the server - you need to abstract the principles used here and include into your own client code.
A component description file, i.e. a bld.inf
file that
If you have a devkit, then you can find a copy of the source in the directories under:
...\Base\IPC\ClientServer\Gettingstarted\bld.inf
...\Base\IPC\ClientServer\Gettingstarted\Transient\src
...\Base\IPC\ClientServer\Gettingstarted\Transient\test
...\Base\IPC\ClientServer\Gettingstarted\Transient\EABI
...\Base\IPC\ClientServer\Gettingstarted\Transient\BWINS
The source code is also included within this documentation; you can find it in the following locations: bld.inf, src, test, EABI, and BWINS.
The client interface effectively forms the gateway to the server. All requests to the server, including requests to connect to it are routed through this; it is built from the following files:
This file defines the essential elements of the project that builds the client interface DLL. You import this into your IDE and build it.
The main points to note are:
source client.cpp
this is the name of the file containing the source code. In practice, you might spread your implementation across a number of source files.
library euser.lib
this means that the client interface only uses the functionality provided by the User Library. In practice you are likely to need services provided by other DLLs, i.e. other parts of Symbian OS, and means that you would need to add to this list.
capability All -Tcb
this means that the DLL requires all capabilities supported by the current version of Symbian OS but excludes TCB capability. Remember that DLL capabilities are different to the capabilities attached to executable processes in that they only reflect the level of trust that the loading process can place in them; they don’t actually authorise anything.
targettype dll
This simply states that we are building a DLL.
target t-client.dll
This is simply the name of the DLL
See also : Platform security, Build tools guide, How to build DLLs and MMP file syntax
This is the header file that defines the classes used by the client interface.
The client interface defines just one class:
RMySession
derived from RSessionBase
. An
RSessionBase
(derived) object represents the client side session
and forms a channel of communication between the client and the server.
Requests to the server, and information received from the server are channelled
through this object. Note that the client side session object is mirrored by a
server side session object, an instance of a class derived from
CSession2
, which in this server is called
CMySession
.
The main points to note are:
The client calls Connect()
to set up a connection
to the server. This function attempts to start the server if necessary and then
issues a request to create a session with the server. If the server fails to
start, or a session cannot be created then this is reported back. If this
function completes successfully, then you can be sure that the connection has
been established, and that you now have a session with the server.
The client calls Send()
in the client interface to
send a message to the server. This is implemented by calling down to the
synchronous variant of RSessionBase::SendReceive()
in the
base class. Synchronous means that it does not return until the server has
dealt with the request.
Send()
passes an operation code (or function
number) identifying the request and a descriptor containing information to the
server. The descriptor is wrapped into a TIpcArgs
object.
In practice, your client interface would probably be richer, having variants of
the Send()
function that take more than one parameter. You might
also choose to have asynchronous variants of Send()
; your client
code (not the client interface) would then need to use
active objects
to deal with this.
The client calls Receive()
in the client interface
to request information from the server. This is implemented by calling down to
the asynchronous variant of RSessionBase::SendReceive()
in
the base class. Asynchronous means that the call returns immediately, but the
request itself may not complete until some time later; see
active objects
for more information.
Receive()
passes an operation code (or function
number) identifying the request, a descriptor into which the server will place
the information, and a TRequestStatus
object, which the
server will use to report how the request completes. The descriptor is wrapped
into a TIpcArgs
object. Again, you might want a richer
client interface; perhaps different function names to represent different
request types.
CancelReceive()
allows the client to cancel a
pending Receive()
request. It is usual to provide some mechanism
to cancel asynchronous requests. This is implemented by calling down into the
asynchronous variant of RSessionBase::SendReceive()
in the
base class. Note that in Receive()
simply passes an operation code
(or function number) and no data.
The operation codes (or function numbers) are defined by an
enum in the header file clientserver.h
. This file is included in
both the client interface code and server code as both need access to these
common symbols.
This file contains the implementation for the client interface.
The main point of interest here is how the Connect()
function is implemented. It attempts to create a session with the server first,
and if this fails because the server does not exist or is in the process of
closing down, then the function attempts to start it. Simultaneous launching of
two such server processes will be detected, with the second attempt failing
with KErrAlreadyExists
.
This file defines the essential elements of the project that builds the server. You import this into your IDE and build it.
The main points to note are:
source server.cpp
this is the name of the file containing the source code. In practice, you might spread your implementation across a number of source files.
library euser.lib
this means that the server only uses the functionality provided by the User Library. In practice you are likely to need services provided by other DLLs, i.e. other parts of Symbian OS, and means that you would need to add to this list.
capability None
this means that the server requires no specific capabilities.
If you need specific capabilities, for example LocalServices to
allow your server to access APIs that are involved in the management of the
user's privacy with regard to the phone location, then you would change this
line to capability LocalServices
.
uid 0 0x01000000
UIDs are not normally required in a .mmp
file
representing a .EXE
. However, in the interests of platform
security, all processes should have a Secure Identifier; this a 32-bit value
defined using the secureid keyword.
Alternatively, if this keyword is not present, then the UID3 value is used.
Here, this is the value 0x01000000. Note that this value is for demonstration
purposes; in practice you need to contact
Symbian Signed for your own
UID values.
t-server.exe
This is simply the name of the server executable.
See also : Platform security, Build tools guide, How to build EXEs and MMP file syntax
This is the header file that defines the classes, function prototypes, enums etc used by the server.
The main points to note are:
class CMyServer
This is the heart of the server implementation. It derives from
the Symbian OS class CServer2
. The main role of this class
is to handle messages sent by client threads; this includes requests from
clients for connection to the server. This is an
active object
(Symbian OS's implementation of non-preemptive multitasking within the context
of a single thread).
One of the most important behaviours provided by this class is
dealing with requests for connection by clients. Connecting to the server means
starting a session with the server. The session is represented by a session
object, in this case the CMySession
object.
class CMySession
One of these objects is created for each session, i.e. as a
result of a request by a client for connection to the server. Once sessions
have been created, requests received by CMyServer
are then
forwarded to the corresponding session, and specifically to the virtual
function ServiceL()
. An implementation of this function is
provided by CMySession::ServiceL()
. The implementation of
CMySession::ServiceL()
in the form of a switch
statement is typical.
class CShutdown
this represents a timer that is started before the first client has connected, and after the last client has disconnected. The timer is stopped once the server has at least one client. If the server has no clients, then there is 2 second interval before the server terminates. This is implemented as an active object.
This file contains the server implementation.
There are number of points of interest here:
The server is an executable, and E32Main()
is the
entry point. After setting up heap checking (in debug builds only) the
implementation creates a cleanup stack and then executes the main body of the
code inside a trap harness.
The implementation initialises the server by creating and
initialising the CMyServer2
object. Once this object has been
created, it is then ready to receive messages from (potential) clients,
including requests for connection.
At this point the active scheduler can be started by the call
to CActiveScheduler::Start()
. This call will not return
until the active scheduler is stopped by the CShutdown
timer,
which is primed after the last client has disconnected. In simple terms, the
active scheduler is a wait loop. It sits inactive until it receives a message
from (potential) clients (or until the timer completes and stops the active
scheduler). Messages that represent requests for connection result in the
creation of a session. Other messages are passed to the relevant
CMySession
active object.
See also Build tools guide, How to build EXEs and MMP file syntax