Symbian OS之客户-服务器架构
活动对象中需要的所有异步服务,都是服务器通过客户-服务器架构来提供的。
上图中Kernel Server是灰色的,表示内核服务器不能被用户直接访问使用,它是用来管理其他服务器的。
服务器一般采取插件结构,这样更容易实现模块话,容易管理。
客户-服务器架构允许:
可扩展性:可以添加新插件模块以服务于新对象类型。
有效性:相同的服务器可以服务于多个客户。
安全性:因为一般情况下服务器及其客户存在于单独的进程中,并且通过消息传送进行通信。
异步性:因为服务器使用活动对象框架来通知它们的客户工作何时完成。
一、服务器会话:
R类没有公共基类,但是R类具有公共基类的服务器会话:RsessionBase。
RSessionBase提供了在客户和服务器之间通信相对应的部分API。通常不需要直接使用这个基类。
二、服务器会话和进程间通信
我们实际使用的是Client Dll的api,Client Dll好像就是服务器在客户端的一个代理,通过它我们间接的使用服务器提供的服务。
Symbian OS内核和内存管理单元为每个进程实现不同的内存映射地址空间,同时防止无关进程重写另一个进程的内存。 唯一能够“看到”整个物理内存的进程是内核进程自身。所有的线程都是内核服务器的客户,并且正是内核帮助进程和它们所包含的线程之间进行通信。
class RSessionBase : public RHandleBase
{
protected:
inline TInt CreateSession(const TDes& aServer , const TVersion& aVersion);
TRequestStatus aStatus) const;
TInt SendReceive(TInt aFunction , TAny* aPtr) const;
}
服务器会话使用CreateSession()来连接到它的服务器。通过名称指定服务器,而且内核可以使用这个名称建立连接。可见,该函数为受保护函数,并且RFs::Connect()这样方法将包装这种调用,并且提供一个简单的客户端API,从而调用者实际上并不需要知道它所连接的服务器名称。
然后,可以将这种连接认为是客户和服务器之间的“管道”,通过这种连接路由所有的通信。通信自身采用消息的形式,通过SendReceive()方法从客户中传递消息。每个重载的方法,带有一个函数ID(作为一个TInt)和一个指针,该指针指向具有4个32位值的数组。
在服务器端,客户的消息表示为一个RMessage对象:
class RMessage
{
public:
void Complete(TInt aReason) const;
void ReadL( const TAny* aPtr , TDes8& aDes) const;.//将数据读入服务器的地址空间中
void ReadL( const TAny* aPtr , TDes16& aDes) const;.
void WriteL( const TAny* aPtr , TDes8& aDes) const;. //将数据写回客户
void WriteL( const TAny* aPtr , TDes16& aDes) const;.
TInt Function() const;//返回客户请求的函数ID值
TInt Int0() const;//四个相同数据项的不同解释
TInt Int1() const;
TInt Int2() const;
TInt Int3() const;
const TAny* Ptr0() const;
const TAny* Ptr1() const;
const TAny* Ptr2() const;
const TAny* Ptr3() const;
}
一旦请求完成,用适当的错误码(很可能是KErrNone)调用Complete()。在请求同步函数的情况下,这将称为SendReceive()的返回值。
如果请求异步函数,这将是TRequestStatus的值。
服务器如何首先获得消息?
下面是服务器端对象的基类,这些对象代表与客户的会话:
class CSharableSession : public CBase
{
friend class Cserver;
public:
virtual void CreateL(const cServer& aServer);
virtual void ServiceL(const RMessage& aMessage) = 0;
const CServer* iServer;
}
当客户初始连接时,服务器创建一个会话。它调用CreateL()完成构造,同时传入自身的引用,允许会话访问它。
当客户发布请求时,服务器在相关的会话上调用ServiceL()函数,正是该方法调用适当的功能,由消息的函数ID确定这些功能,这些功能作用于消息中的任何数据。
出于完整性考虑,下面列出CServer的一些声明:
class CServer : public CActive
{
public:
IMPORT_C void StartL(const TDesC& aName);
IMPORT_C void DoCancel();
IMPORT_C void RunL();
}
注意,所有的服务器都是从CActive派生而来――它们都是活动对象。StartL()方法将它们添加到活动规划器中,并且启动它们等待它们的初次请求。实际上由RunL()的实现负责在会话上调用ServiceL()。当内核通过信号通知服务器的TRequestStatus(表示已从客户传入请示)时执行。
服务器自身如何启动?
这取决于服务器的特性。在引导系统时启动许多基本的服务器,并且它们总是处于可用状态。其他服务器,特别是在后面添加到系统中的服务器,需要在第一次发布请求时,由它们的客户API显式的启动。这种类型的服务器通常保持对客户数量的计数,当计数降为0时,该服务器自动卸载自己。
三、服务器综述:(1、3、15涉及客户)
下面给出了服务器服务于异步请求的基本过程,假设已经启动了所讨论的服务器。
1、 客户通过 RSession 派生对象建立与服务器的连接,该对象表示客户端的 API
服务器创建与客户会话关联的新会话对象
3、 客户通过客户端的 API 建立请求
根据具体的请求,客户API将与请求关联的所有数据包装为整数、描述符或包描述符。
客户API调用SendReceiveL(),该函数带有适当的函数ID和参数指针。
内核完成服务器的iStatus成员。
调用服务器的RunL(),通过内核将从客户处接收到的消息传递给会话的ServiceL()方法。
根据消息中函数ID值,会话的ServiceL()调用适当的服务处理器。
使用适当的ReadL(),服务处理程序从客户的地址空间中复制数据。
服务处理程序服务请求
使用适当的WriteL(),服务处理器将数据复制到客户地址空间。
服务处理器在消息上以适当错误码,调用Complete()。
内核增加客户线程的信号量,并且将TRequestStatus设置为Complete()中指定的值。
客户线程的活动规划器调用客户的RunL()方法。
15、 客户的 RunL ()通过客户 API 执行对返回数据的所有处理。
四、子会话
每次打开会话时,需要消耗更多的内核资源。
RFile 是一个子会话。子会话是一种让客户与服务器进行通信而不需要单个会话来表示每个客户端对象的轻量级方法。客户与服务器建立单个会话,然后为每个对象创建子会话。每个子会话和会话相关联,借此进行实际的进程间通信。
子会话基类RSubSessionBase
class RSubSessionBase
{
protected:
TInt CreateSubSession(RSessionBase& aSession , TInt aFunciton , const TAny* aPtr);
void CloseSubSession(TInt aFunction);
void SendReceive(TInt aFunction , const TAny* aPtr , TRequestStatus& aStatus) const;
TInt SendReceive(TInt aFunction , const TAny* aPtr) const;
private:
RSessionBase iSession;
TInt iSubSessionHandle;
}
五、服务器资源的释放