分类: C/C++
2009-04-16 09:23:00
以下分析基于:
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来做。
四,实例剖析
下面以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的交互就这样结束了。
这里的分析忽略的所有的错误处理的流程,具体的处理请参考相应的代码。其实错误处理非常重要。