Symbian OS中的Client/Server机制分析(转)

以下分析基于:

Developer Platform :S60 3rd Edition, Feature Pack 2 SDK

Operating System :Symbian OS v9.3

一,为什么要使用Client/Server架构

在Symibian OS中所有的异步服务都是Server通过C/S架构来实现的。Client是利用Server提供的特定服务的程序,Server接受来至Client的请求消息并同步或异步的处理他们。C/S架构有如下的优点:

1,可扩展性

2,有效性:相同的Server可以服务多个Client。

3,安全性:Server和Client存在于单独的进程中,并且通过消息传递进行通信。具有错误行为Client不会使他的Server崩溃(但是,Server可以通过客户线程的句柄来是具有错误行为的Client产生严重错误)。

4,异步性:在服务器完成请求的时候使用AO机制来通知他的Client。通过AO来挂起线程而不是轮询请求的状态,Symbian OS减少了处理该请求的处理器周期,从而节约了电源,这对于移动设备来说是非常重要的。

二,Client/Server架构的处理流程

Clinet和Server处于不同的进程中,他们无法访问彼此的虚地址空间,所以他们使用消息传递协议来通信,这种通信的渠道就称为会话。会话由内核创建,同时内核还在所有的Client/Server通信中充当媒介。

服务,特别是系统提供的服务,比如:文件服务,窗口服务和字体和位图服务等都是在系统启动的时候就启动了。当然如果是自己做的server可以在需要的时候,即当有client发出请求的时候再启动。然后服务器阻塞在某个点上,等待client请求的到来。在Client发出一个请求后,服务器会new一个子会话来处理这个client的请求,然后自己又继续阻塞在监听请求的点上,以满足其他Client的请求。每个Client和Server的后续交互都是通过连接Server时创建的Session来完成的。

三,R类介绍

C/S架构免不了要使用R类,那么什么是R类呢?这里的"R"是Resource的第一个字母,是资源的意思。

1,简介

具有"R"前缀的类表示某个资源的客户端句柄。应用程序实际上并不拥有资源,资源由设备上一个Symbian OS 服务器所拥有,服务器管理资源的使用。客户可以使用这些句柄访问服务器管理的资源,并请求使用它的功能。

R 类没有任何公共的基类,有些分块的派系类,比如,有些类是从RHandleBase派生—RFs,而有的什么都不是—RWindow等。一般来说,R类是在栈上实例化或是嵌套在C类中,然后通过某种方式打开他们(通常通过一个方法调用来打开,例如调用Open()或Connect())。结束使用它们时,有必要使用适当的方法来处理这些类(通常使用Close()函数)。如果在完成这项工作时失败,则R类所连接的服务器内存和其他资源泄漏。

2,深入

Symbian 里的R类到底是啥?基本上所有R类都包含一个指向kernel-side object的handle,这个handle是一个整数!通过这个整数,在DObjectIx这个容器中可以索引到R类指 向的对象,这些对象都是从DObject派生出来的,具有引用计数的功能!当user-side的R类调用Close方法后,对应的计数减1,到0时,就会自动析构内核的对象。
那么内核是如何把一个handle的32位整数解析成一个指向内核对象的指针的呢?
struct SDObjectIxRec
{
TInt16 instance;
TInt16 uniqueID;
DObject* obj;
};
这些指针通过SDObjectIxRec包装,放到DObjectIx的数组中,每一个DProcess或者DThread都有一个这样一个数组!(所以,当你创建一个R类的时 候需要你指明是thread范围的还是process范围的,他们会被放到不同的地方)。
1.根据BIT30看这个handle是存在thread里还是process的DObjectIx里
2.根据这个handle的BIT0-14来判断这个handle真的在DObjectIx里,数组不越界
3.根据BIT0-14获取这个index对应的SDObjectIxRec
4.根据BIT16-29和获得SDObjectIxRec的instance比对
5.如果一致就无措,这些操作可能会需要NKern::LockSystem()
6.获得SDObjectIxRec的obj指针

三,相关类的分析

CServer2 是一个AO,从客户线程接受请求,然后把这些请求分发到相应的服务器端的客户session。它可以利用客户线程请求来创建服务器端的客户线程。
/**
Abstract base class for servers (version 2). A server must define and implement a derived class. (Note that this class should be used instead of CServer)
*/
class CServer2 : public CActive
{
public:
IMPORT_C virtual ~CServer2() =0;
IMPORT_C TInt Start(const TDesC& aName);
IMPORT_C void StartL(const TDesC& aName);
IMPORT_C void ReStart();

/**
Gets a handle to the server.

Note that the RServer2 object is classified as Symbian internal, and its member functions cannot be acessed. However, the handle can be passed to the RSessionBase::CreateSession() variants that take a server handle.

@return The handle to the server.
*/
inline RServer2 Server() const { return iServer; }
protected:
inline const RMessage2& Message() const;
IMPORT_C CServer2(TInt aPriority, TServerType aType=EUnsharableSessions);
IMPORT_C void DoCancel();
IMPORT_C void RunL();
IMPORT_C TInt RunError(TInt aError);
private:
/**
Creates a server-side session object.

The session represents a communication link between a client and a server, and its creation is initiated by the client through a call to one of the RSessionBase::CreateSession() variants.

A server must provide an implementation, which as a minimum should:

- check that the version of the server is compatible with the client by comparing the client supplied version number against the server's version number; it should leave if there is incompatibility.

- construct and return the server side client session object.

@param aVersion The version information supplied by the client. 
@param aMessage Represents the details of the client request that is requesting the creation of the session.
@return A pointer to the newly created server-side session object. 
@see User::QueryVersionSupported()
*/
IMPORT_C virtual CSession2* NewSessionL(const TVersion& aVersion,const RMessage2& aMessage) const =0;
...
...
};

有两个API需要我们注意一下:
IMPORT_C void StartL(const TDesC& aName);

SDK 中描述是把指定名称的Server加入到AS中,并且触发一个请求。一般这个函数会在我们自己写的类的ConstructL()中调用。因为 CServer2是从CActive派生的,所以我们可以猜测在StartL中,会触发一个请求,然后调用CActive的方法SetActive来表明 AO启动了请求。
一般,系统的服务器,比如:文件服务器,字体和位图服务器和窗口服务器等都是在系统启动的时候就启动了。但是,如果我们自己的服务器不希望如此,只是在必要的时候,即当有Client发出request的时候再启动 server,我们就需要在ConstructL()中手动调用这个函数了。

IMPORT_C virtual CSession2* NewSessionL(const TVersion& aVersion,const RMessage2& aMessage) const =0;

岽唇ㄒ桓鯯erver端的Session,这个Session代表一个Client和一个Server的通信连接,它可以通过RSessionBase:: CreateSession() 的调用来创建和初始化。也就是说,如果Client端调用了RSessionBase::CreateSession(),那么Server端的 NewSessionL就会被调用。猜测下Server的实现如下:当Client端调用了RSessionBase::CreateSession (),内核找到相应的Server,然后Server线程的AS会check这个Client在Server端有没有对应的Session,如果没有就调用NewSessionL来创建一个Session。Client和Server后续的数据操作都通过这个Session来完成。

当我们在设计自己的Server的时候,我们需要从CServer2类继承,CServer2是一个AO是从CActive派生的,他实现了CActive 的几个纯虚/虚函数,我们的Server类必须实现NewSessionL这个纯虚函数,当然如果你仅仅实现这个也是useless的。一般我们会重写 CServer2的RunError的实现,但是不会去重写RunL和DoCancel方法。

通常,我们给Server传递一个请求,让Server做某些修改后在回传给我们,所以在SendReceive函数中不要传递局部变量的描述符,通常是用一个data member来做。

CSession2 服务器端的客户Session, 充当 Client和Server 的通信信道,一个Client线程能和一个Server并发多个线程。
/**
A session can be:
- restricted to the creating thread
- can be shared with other threads in the same process
- can be shared by all threads in the system.
A server must define and implement a derived class. In particular, it must provide an implementation for the ServiceL() virtual function.
(Note that this class should be used instead of CSession)
*/
class CSession2 : public CBase
{
public:
   IMPORT_C virtual ~CSession2() =0;
public:
   inline const CServer2* Server() const;
   IMPORT_C void ResourceCountMarkStart();
   IMPORT_C void ResourceCountMarkEnd(const RMessage2& aMessage);
   IMPORT_C virtual TInt CountResources();
/**
Handles the servicing of a client request that has been passed to the server.
This function must be implemented in a derived class. The details of the request are contained within the message.
@param aMessage The message containing the details of the client request.
*/
virtual void ServiceL(const RMessage2& aMessage) =0;
   IMPORT_C virtual void ServiceError(const RMessage2& aMessage,TInt aError);
...
...
};
virtual void ServiceL(const RMessage2& aMessage) =0;
这个是纯虚函数,所以派生类必须做实现。当Client在建立了和Server的连接后,会通过某个接口向Server发送数据请求等操作,Server端的Session的虚函数ServiceL()会被调用,在参数RMessage2中会包括Client的请求信息。根据 aMessage.Function()的值做相应的处理。处理完后调用aMessage.Complete( KErrNone );来告知Client处理结束了,通常是增加请求线程上的信号量来告诉Client请求的完成。
既然,Server端有个Session,那么Client端也有个相应的Session了,答案是肯定的。它就是RSessionBase ,它是Client端的Session句柄,通过这个Client端的接口就可以和Server通信了。
/**
Clients normally define and implement a derived class to provide a richer interface.
*/
class RSessionBase : public RHandleBase
{
    friend class RSubSessionBase;
    。。。
    IMPORT_C TInt Open(RMessagePtr2 aMessage,TInt aParam,TOwnerType aType=EOwnerProcess);
    IMPORT_C TInt Open(RMessagePtr2 aMessage,TInt aParam,const TSecurityPolicy& aServerPolicy,TOwnerType aType=EOwnerProcess);
    IMPORT_C TInt Open(TInt aArgumentIndex, TOwnerType aType=EOwnerProcess);
    IMPORT_C TInt Open(TInt aArgumentIndex, const TSecurityPolicy& aServerPolicy, TOwnerType aType=EOwnerProcess);
    inline TInt SetReturnedHandle(TInt aHandleOrError);
    IMPORT_C TInt SetReturnedHandle(TInt aHandleOrError,const TSecurityPolicy& aServerPolicy);
protected:
    inline TInt  CreateSession (const TDesC& aServer,const TVersion& aVersion);
    IMPORT_C TInt  CreateSession (const TDesC& aServer,const TVersion& aVersion,TInt aAsyncMessageSlots);
    IMPORT_C TInt  CreateSession (const TDesC& aServer,const TVersion& aVersion,TInt aAsyncMessageSlots,TIpcSessionType aType,const TSecurityPolicy* aPolicy=0, TRequestStatus* aStatus=0);
    inline TInt CreateSession (RServer2 aServer,const TVersion& aVersion);
    IMPORT_C TInt CreateSession (RServer2 aServer,const TVersion& aVersion,TInt aAsyncMessageSlots);
    IMPORT_C TInt CreateSession (RServer2 aServer,const TVersion& aVersion,TInt aAsyncMessageSlots,TIpcSessionType aType,const TSecurityPolicy* aPolicy=0, TRequestStatus* aStatus=0);
    inline static TInt SetReturnedHandle(TInt aHandleOrError,RHandleBase& aHandle);
    inline TInt  Send (TInt aFunction,const TIpcArgs& aArgs) const;
    inline void  SendReceive (TInt aFunction,const TIpcArgs& aArgs,TRequestStatus& aStatus) const;
    inline TInt  SendReceive (TInt aFunction,const TIpcArgs& aArgs) const;
    inline TInt  Send (TInt aFunction) const;
    inline void  SendReceive (TInt aFunction,TRequestStatus& aStatus) const;
    inline TInt  SendReceive (TInt aFunction) const;
    。。。
};
这个类提供了很多重载的CreateSession ()方法,根据需要进行选择,涉及到几个概念就是消息槽和IPC Session的类型,可以根据SDK的描述使用,SDK有详细的阐述,没有用到过,这里就不详述了。
SendReceive ()也有很多重载的方法,主要是同/异步的选择,那些没有 TRequestStatus参数的API是同步的,一个Client的Session对Server同时只能有一个当前的同步请求,异步请求可以有多个。

RMessage2
这个类封装了Client请求的细节,在Client和Server端传递。
/**
An object that encapsulates the details of a client request.
*/
class RMessage2 : public RMessagePtr2
{
    friend class CServer2;
#endif
    inline TInt Function() const;
    inline TInt Int0() const;
    inline TInt Int1() const;
    inline TInt Int2() const;
    inline TInt Int3() const;
    inline const TAny* Ptr0() const;
    inline const TAny* Ptr1() const;
    inline const TAny* Ptr2() const;
    inline const TAny* Ptr3() const;
    inline CSession2* Session() const;
protected:
    /**
    The request type.
    */
    TInt iFunction;
};
这里的 Function ()对应于RSessionbase:: SendReceive (TInt aFunction,const TIpcArgs& aArgs,TRequestStatus& aStatus) 中的aFunction。Client端通过TIpcArgs这个结构包装数据向Server发送请求,TIpcArgs 支持0到4个参数,最多只能是4个,如果参数是简单的整型值我们可以通过RMessage2的Int0-3这些个API得到他们的值,如果是其他类新,比如是描述符,那么就的用Ptr0-3()来获得指针了。系统内部会把TIpcArgs的内容封装成RMessage2,并在Server端相应 Session的ServiceL()中进行解析。

四,实例剖析
下面以SDK中附带的example--ClientServerAsync来阐述一下Client/Server的流程。
1, 在csayncdocument.cpp 的CEikAppUi* CCSAsyncDocument::CreateAppUiL()中会调用CCSAsyncRequestHandler::NewL( *appUi ) 来实例化 CCSAsyncRequestHandler,这个 handler是个AO,它拥有一个 RTimeServerSession iSession数据成员, RTimeServerSession继承自客户端的Session类 RSessionBase。这ConstructL()中会调用iSession.Connect() ;
2, RTimeServerSession的Connet() 方法会连接到Server并且创建一个Session。它首先调用一个静态函数StartServer() ,其中会通过TFindServer的方法来来检测Server是否启动,如果没有则会调用RProcess的Create() 方法来启动这个新的进程,通过Server的名字,如果这个名字没有追加.exe那么API会帮你做这个事情,如果没有指明这个可执行文件的绝对路径,那么系统会在所有的\sys\bin目录下找,我们还会传入和可执行文件匹配的UID3。
3, Server的启动,server的TInt E32Main()作为程序的入口开始run,在Server的ThreadFunctionL()中,首先会创建一个CActiveScheduler --活动对象调度器,例子里面的Server--CTimeServer从CServer2继承,而CServer2是一个活动对象,所以 CTimeServer也是一个封装后的活动对象。
4,在CTimeServer::ThreadFunctionL() 中调用CTimeServer::NewLC() 来构造Server,在 CTimeServer的ConstructL()中调用基类CServer2的方法StartL() 来启动AO,在这个方法里面,我们猜测会发一个请求,等待Client端的连接,并调用AO的基类CActive的方法SetActive来表明他触发了一个请求。然后,会调用CActiveScheduler::Start()让AS来处理请求。此时的Server已经启动完毕,等待Client发出创建 Session的请求。
5,Client在RTimeServerSession::Connect()里面继续调用RSessionBase重载的方法之一CreateSession()来创建一个Client端的Session来和Server通信。
6,Server的AS在收到这个请求后,会调用CServer2这个AO的RunL(),在这个函数里面会调用它的一个需要派生类做实现的虚接口NewSessionL() (此处是猜测CServer2的实现 ),在这个函数里面Server会用CTimeServerSession::NewL()创建一个表示这个Client连接的Session。此时Client和Server端连接的通道,两个Session的实例都已创建,Client和Server可以通信了。
Client 的Session是从RSessionBase继承的,由一个handler(其实它是一个AO)拥有;Server端的Sessin是从 CSession2继承的,它有Server创建,然后会放到CServer2的这个数据成员里面:TDblQueIter< CSession2> iSessionIter;而CServer2,则又是一个AO。这里是否可以简单的理解为AO来使用Session??
7,此时Server已经启动,Client也启动了,Client和Server的通信线路也已经建立起来了。在Appui里面点菜单启动时钟,会调用 AsyncDocument()->UpdateTime();在Document里面会调用iHandler->RequestTime (); Handler这个AO,首先会检测自己是否还处于活动状态(通过AO的基类方法IsActive()),如果没有的话,就通过Client端的 Session,触发一个请求,然后调用AO基类的方法SetActive()激活自己。
8,Client端的Session通过调用SendReceive( ETimeServRequestTime, args, aStatus ) 这个API向Server发出请求,其中的args是一个TIpcArgs 的类型参数。Server端的Session阻塞在ServiceL() 处等待Client的请求,然后根据aMessage.Function()的值分别进行处理。以case ETimeServRequestTime为例子,先在server端保留一份消息的内容--iMessage,然后向Server请求它的 CHeartbeat,然后会调用iHeartbeat->Start( ETwelveOClock, this );来启动heartbeat
9,当请求的完成是会调用接口类MBeating的方法virtual void Beat() =0; 也即Server重载的方法。在SendTimeToSessions中会向iSessionIter这个队列的第一个Session发出回应,通过 Server端Session的session->SendTimeToClient();方法,具体的动作是调用保存的消息的Write方法,即iMessage.WriteL( 0, ptr, 0 ) 来传递给Client并调用iMessage.Complete( ETimeServRequestTimeComplete ); 来标称服务的完成。
10,Client端的Handle的AO的RunL()会被调用根据 iMessage.Complete()中参数进行相应的处理 。首先是iObserver.HandleTimeUpdate(); 也就是CCSAsyncAppUi::HandleTimeUpdate(),这个方法直接会调用iAppView->DrawNow();在 view的Draw()方法里面会取 iDocument.Time(),实际上是取的iHandler->Time(),即Handle的成员TTime iTime;
11,一次请求完成之后在Handle中会调用RequestTime(); 再次出发请求。一个完成的Client/Server的交互就这样结束了。
这里的分析忽略的所有的错误处理的流程,具体的处理请参考相应的代码。其实错误处理非常重要。

原文地址:https://www.cnblogs.com/yaoliang11/p/1813579.html