跟着Code走,详解Symbian Client/Server架构

来源:互联网 发布:猫群算法代码 编辑:程序博客网 时间:2024/05/06 18:45

Client/Server架构是Symbian下最主要的进程间通信方法。程序如果需要后台服务,并与前台程序交互,一般都是通过Client/Server的方式实现。程序如果需要使用系统的服务,例如访问文件,也需要通过RFile等User Library,建立与系统Server的连接,然后操作。根据Symbian的开发文档,很快就可以完成Client/Server通信的基本代码,但是这些代码背后的详细操作过程是怎样的呢?本文将和你一起弄清楚其中的详细交互过程。

我们首先列出一个最基本的Client/Server通信代码样例,然后分析这些代码背后的动作。

 

【Server编程代码】

如果要实现一个Server,首先要定义一个派生自CServer2的类,并实现必须的NewSessionL虚函数。NewSessionL在CServer2中定义为纯虚函数,派生类必须实现。需要注意的是CServer2本身是派生自CActive的,Server本身使用ActiveObject实现的。

class CTestServer : public CServer2 
{ 
private: 
    virtual CSession2* NewSessionL(const TVersion& aVersion,const RMessage2& aMessage) const; 
 
}

初始化过程中,各种初始化操作完成后,必须调用基类CServer2的StartL("MyTestServer");函数在内核中注册Server,传入的参数是Server的名字。之后所有的Client尝试连接时,可以通过这个名字找到Server。

为了处理Client的请求,Server还必须实现派生自CSession2的的session类。Server类在NewSessionL函数中,必须新建session类并返回。Client连接Server时,框架会自动调用NewSessionL函数,之后Client的所有命令都由新建的session对象处理。

session对象必须实现ServiceL函数,Client发出请求时,框架会调用session对象的ServiceL函数进行处理。

class CTestSession : public CSession2 
{ 
public:  
    void ServiceL(const RMessage2& aMessage); 
 
}

【Client编程代码】

然后再看看Client需要连接Server并请求操作时,需要完成的代码。Client必须实现派生自RSessionBase的类,并提供连接Server的函数和向Server发送命令的函数。

class RTestClient : public RSessionBase 
{ 
public: 
    TInt Connect(); 
    TInt SendRequest(TIng arg); 
 
}

连接Server的函数可以直接通过调用基类的CreateSession函数,建立与Server的连接。需要注意的是如果发现Server程序还没有启动,首先需要把Server程序启动。向Server发送命令的函数通过调用基类的SendReceive函数实现,这个函数的参数为一个请求码和请求参数。

需要注意的是SendReceive函数主要有两个重载版本,如下。带TRequestStatus参数的是异步版本,调用后该函数立即返回,请求完成后会向请求线程发送RequestComplete的信号。

void RSessionBase::SendReceive(TInt aFunction,const TIpcArgs& aArgs,TRequestStatus& aStatus) const

TInt RSessionBase::SendReceive(TInt aFunction,const TIpcArgs& aArgs) const

以上就是Client/Server通信的最基本代码,当然其中很多细节都没有包括,你可以查看Nokia的文档了解详细代码。

 

【Server用户态实现代码】

Server用户态实现代码主要包括两个部分:一是Server本身的初始化,在内核中创建DServer对象;二是从内核中获取需要处理的Message并进行处理。

  • 【Server初始化】

CServer2::StartL函数的实现代码在Symbian OS源码中的Kernal Package的文件kernel/eka/euser/cbase/ub_svr.cpp中,如下。CServer2有一个成员变量RServer2 iServer;这个成员变量保存了当前Server在内核中对应的DServer对象的handle。下面代码中的第一句iServer.CreateGlobal就是创建内核DServer对象。然后会把自己放入CActiveScheduler中(前面说过CServer2本身是AO)。

EXPORT_C TInt CServer2::Start(const TDesC& aName) 
    { 
    TInt r = iServer.CreateGlobal(aName, iSessionType, iServerRole, iServerOpts); 
    if (r == KErrNone) 
        { 
        CActiveScheduler::Add(this); 
        ReStart(); 
        } 
    return r; 
    }

ReStart函数通过内核DServer句柄,调用异步函数获得需要处理的Message,然后调用SetActive表示AO激活。

EXPORT_C void CServer2::ReStart() 
    { 
    iServer.Receive(iMessage,iStatus); 
    SetActive(); 
    }

  • 【从内核获取Message并处理】

当从内核DServer获取到需要处理的Message后,void CServer2::RunL()会被调用。(如果一直没有需要处理的Message,那么异步函数便一直不返回,直到有Message需要处理为止)void CServer2::RunL()根据Client的请求码进行处理。请注意iMessage也是R类的成员变量,它只是内核中message的handle,并不是message本身。

小于0的请求码是建立连接或者断开连接的请求。如果是连接请求,内核态会创建DSession对象,并放入DServer对象的session列表中,用户态会调用NewSessionL函数创建session对象,并放入CServer2的session列表中,同时还会把用户态session对象的指针保存到内核DSession.iSessionCookie中。如果是断开连接请求,会用从Message中得到的session对象指针,complete Client的请求,内核态如果发现时EDisconnect请求,会在真正complete用户态Client的请求前,从DServer的session列表中删除对应的DSession对象,然后删除内核中的DSession对象。

大于等于0的请求码对应Client的其他功能请求。首先从通过message handle从内核中获得对应用户态session对象指针,然后调用CSession2::ServiceL函数。

处理完一个Message后,在void CServer2::RunL()代码的最后,会再次调用ReStart函数进入等待Message状态。

 

【Client用户态实现代码】

Client用户态实现的代码包括初始创建session连接,后续的命令发送操作,及最后的session断开操作,下面我们分别看。

  • 【创建session连接】

创建session连接在用户编程代码中通过调用RSessionBase.CreateSession完成,该函数调用Exec::SessionCreate完成session创建。内核在ExecHandler::SessionCreate函数中处理session创建请求,在正式创建session之前,首先会检查capability。调用RSessionBase.CreateSession,仅仅触发内核创建了DSession对象,用户态Server还并未创建CSession2对象。RSessionBase.CreateSession会接着调用RSessionBase.DoConnect,该函数向Server发送RMessage2::EConnect。在前面【Server用户态实现代码】中已经提到,用户态如果收到RMessage2::EConnect,会创建CSession2对象。

  • 【发送Message到Server】

Client调用RSessionBase.SendReveive->RSessionBase.DoSendReceive->RSessionBase.SendSync->Exec::SessionSendSync(iHandle,aFunction,(TAny*)aArgs,&s);完成Message发送。内核对应处理函数为文件kernel/eka/kernel/sipc.cpp中的ExecHandler::SessionSendSync。内核首先会根据session handle从内核对象列表中找到对应的DSession对象,然后把Message放入自己的Message队列SDblQue   iMsgQ;,等待用户态Server从内核获取Message。如果用户态Server已经是处于等待Message状态,内核此时会complete用户态Server获取Message的异步请求。

  • 【断开session连接】

用户代码调用RSessionBase.Close-> ExecHandler::HandleClose断开session 连接。内核态实现代码是文件kernel/eka/kernel/sexec.cpp中的函数ExecHandler::HandleClose。该函数会从经过多个函数调用后,调用DSession.Close,该函数把DSession对象引用计数减1,并向向DServer的Message队列中加一个EDisconnect消息。

用户态Server对EDisconnet并没有多少处理,仅仅立即complete当前正在处理的Client请求。内核在ExecHandler::MessageComplete中处理complete请求的操作,进一步的调用过程为ExecHandler::MessageComplete->DSession::CloseFromDisconnect->DSession::Detach。DSession::Detach会complete Dession.iMsgQ中所有pending的request,如果发现总引用计数减为0,会调用K::ObjDelete删除DSession对象。

NOTE:我并没有看到CServer2在处理EDisconnect过程中delete CSession2对象,也没有看到CServer2在处理新的连接请求时,重用之间new的CSession2,只看到CServer2在析构函数中清理CSession2队列,并delete CSession2对象。不知道是我漏掉了代码,还是确实发现了一个bug。

 

【内核态实现代码】

下面我们再看看Server在内核态的实现代码。

  • 【Server创建过程】RServer2.CreateGlobal

首先从Server启动时调用iServer.CreateGlobal看起。kernel/eka/euser/cbase/ub_ksvr.cpp中有RServer2.CreateGlobal 实现代码,它调用Exec::ServerCreateWithOptions(&name8, aMode, aRole, aOpts);实现。根据 跟着Code走,详解Symbian OS API中的介绍,我们可以找到这个调用在内核中对应的实现代码,在Kernel Package中的文件kernel/eka/kernel/sipc.cpp中,其中比较关键的几句代码如下。主要的动作包括,创建内核DServer对象,设置DServer的名字,并把创建的对象放到全局的对象列表中的EServer类型。

 
DServer *pS = new DServer; 
 
r = pS->Create(); 
 
r = pS->SetName(&n); 
 
r = K::AddObject(pS, EServer); 

  • 【获取Message并处理过程】RServer2.Receive

RServer2.Receive的实现代码在文件kernel/eka/euser/cbase/us_exec.cpp中,它调用Exec::ServerReceive(iHandle, aStatus, &aMessage)实现。其对应的内核实现代码是文件kernel/eka/kernel/sipc.cpp中的函数void ExecHandler::ServerReceive(DServer* aServer, TRequestStatus& aStatus, TAny* aMsg),这个函数调用DServer.Receive获取Message。

DServer.Receive如果发现当前Message队列非空,则会从中获取一个Message并准备返回给用户态。如果Message队列为空,则不会complete用户态Server取Message的请求,当有Client发送Message到内核后,内核才会返回Message给用户态Server,并complete用户态Server的请求。

if (!iDeliveredQ.IsEmpty()) 
        { 
        RMessageK* m = _LOFF(iDeliveredQ.First()->Deque(), RMessageK, iServerLink); 
        Accept(m); 
        }

DServer返回Message的代码中大部分都是为了更新DServer的内部状态,真正把Message相关数据写到用户态空间的过程不太容易看懂。

Accept函数的最后会调用Kern::QueueRequestComplete(iOwningThread, iMessage, KErrNone); –> aRequest->EndComplete(aThread); –> TInt r = NKern::QueueUserModeCallback(&aThread->iNThread, this); 函数QueueUserModeCallback的实现代码如下,它会把RMessageK对象赋值给aThread->iUserModeCallbacks 。

TInt NKern::QueueUserModeCallback(NThreadBase* aThread, TUserModeCallback* aCallback) 
    { 
    if (aCallback->iNext != KUserModeCallbackUnqueued) 
        return KErrInUse; 
    TInt r = KErrDied; 
    NKern::Lock(); 
    TUserModeCallback* listHead = aThread->iUserModeCallbacks; 
    if (((TLinAddr)listHead & 3) == 0) 
        { 
        aCallback->iNext = listHead; 
        aThread->iUserModeCallbacks = aCallback; 
        r = KErrNone; 
        } 
    NKern::Unlock(); 
    return r; 
    }

这样当OS API调用返回时,会有调用UserModeCallback的动作(Symbian OS API调用有这样的机制,OS API调用完成后会检查是否有UserModeCallback需要执行,如果有则执行。详细code请查看文件kernel/eka/kernel/arm/victors.cia中的__ArmVectorSwi函数,请注意UserModeCallback是在内核态执行的),这时会调用K::USafeWrite函数,把数据写入到用户态空间,详细过程请查看代码—文件kernel/eka/kernel/sipc.cpp中的函数RMessageK::CallbackFunc。

  • 【DSession管理】

DServer对象创建后,除了在线程中保存其handle外,还会保存在内核全局对象列表中。这样有Client请求连接时,才能从内核全局对象列表中找到DServer对象。相关代码是文件kernel/eka/kernel/sipc.cpp中函数ExecHandler::ServerCreateWithOptions的下面一段。K::AddObject把DServer对象pS放到内核全局对象列表中的EServer类型中,K::MakeHandle在当前线程中保存DServer对象pS的handle。

    r = K::AddObject(pS, EServer); 
            if (r == KErrNone) 
                r = K::MakeHandle(nameLen ? EOwnerThread : EOwnerProcess, pS); 
            }

DSession对象就不需要全局保存了,只需要保存在线程数据中即可。因为DServer对象中已经保存了DSession对象列表,这保证DSession对象一定可以被找到。文件kernel/eka/kernel/sipc.cpp中函数ExecHandler::SessionCreate有以下两句代码,第一句的作用是把新建的DSession对象放到DServer的session队列(经查看代码,DServer对象并没有直接操作DSession,只是保存了DSession指针而已),第二句的作用是把DSession对象handle保存到线程数据中。请注意,session创建是由Client线程发起的,所以这里是保存在Client线程的内核数据中。

 
r = s->Add(svr, aSecurityPolicy); 
 
if (r==KErrNone) 
        r = s->MakeHandle(); 

Client要发送请求时,内核从当前线程内核数据中可以得到对应DSession对象的handle。你可以从以下函数调用弄清查找DSession对象的过程。

ExecHandler::SessionSendSync->DSession::SendSync->K::ObjectFromHandle

什么时候删除DSession对象呢?前面【断开session连接】已经说明,用户代码调用RHandleBase.Close后触发一系列动作。

  • 【Message管理】

首先需要知道的是Message是放在内核全局数据K::SMsgInfo K::MsgInfo;中的(内核全局数据都定义在文件kernel/eka/kernel/sglobals.cpp中)。MsgInfo是如下定义的数据结构,实际对应一个内存chunk,同时保存了下一个可用的message地址,剩余空闲的message空间等。对MsgInfo本身的操作,都封装在RMessageK中,无非是些内存操作,此处不赘述。

static struct SMsgInfo 
    { 
    DChunk* iChunk; 
    TUint8* iBase; 
    TUint iMaxSize; 
    TUint iCurrSize; 
    DMutex* iMsgChunkLock; 
    RMessageK* iNextMessage; 
    TInt iFreeMessageCount; 
    } MsgInfo;

 

下面我们再看看Message的传递过程。用户态Client调用RHandleBase.SendAsync发送Message到内核,内核的ExecHandler::SessionSend函数开始处理。Message传递其实就是函数调用过程,我们先列出Message传递相关的函数调用过程,如下。(这里我们列出的是异步请求的Message传递过程,同步请求直接使用了线程数据中专为同步请求预留的变量TheCurrentThread->iSyncMsgPt)

ExecHandler::SessionSend->DSession::Send->DSession::Send->DServer::Deliver->DServer::Accept->Kern::QueueRequestComplete->TClientRequest::EndComplete->NKern::QueueUserModeCallback

RMessageK在第一个函数DSession::Send中生成:RMessageK* m = session->GetNextFreeMessage();

第二个DSession::Send函数会把RMessageK放到DSession的iMsgQ列表中。

函数DServer::Deliver中,如果这时用户态Server处于等待Message状态,会调用DServer::Accept返回Message,否则调用RMessageK::SetDelivered把Message放到DServer的SDblQue iDeliveredQ;队列中。

后面的操作就是把RMessageK放到UserModeCallback队列中,OS API调用返回时RMessageK::CallbackFunc会得到调用。Message数据会被写入到用户态Server进程空间。

TClientRequest::EndComplete调用NKern::QueueUserModeCallback之后,还会调用NKern::ThreadRequestSignal(&aThread->iNThread); 这个函数把请求线程的iRequestSemaphore置为有效。这样Client用户线程的User::WaitForRequest就会返回,Client得以继续执行。

 

我们再接下来看complete请求的过程。用户态Server调用RMessagePtr2.Complete完成请求,内核态的ExecHandler::MessageComplete处理该请求。我们只关注普通请求,像EConnect和EDisconnect这种特殊请求,暂时忽略。complete请求的处理过程并不复杂,其实与上面介绍的返回Message给用户态Server一样,这里是返回request结果给用户态Client。

ExecHandler::MessageComplete->Kern::QueueRequestComplete->TClientRequest::EndComplete->NKern::QueueUserModeCallback

 

【Client/Server架构图】

综上所述,用户态Server实际是一个ActiveObject,Client/Server在用户态主要通过内核对象的handle实现操作,具体通信过程都在内核中实现。Client/Server架构在用户态/内核态的实现结构如下图。其中需要留意一点的是,CServer2对象中虽然保存了CSession2对象的列表,但是并未直接使用,当CServer2通过RServer2 handle从内核中拿到Message需要处理时,从Message中可以得到内核保存的session cookie,实际就是用户态CSession2对象指针。

SymbianOSInternalsBook_4.2

(摘自Symbian OS Internals 第四章Inter-thread_Communication)

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 幼兔拉稀怎么办没精神 自酿米酒酸了怎么办 用冰敷脸感觉红烫了怎么办 吃了海兔的内脏怎么办 吃了鱿鱼的吸盘怎么办 想开个烧烤店没学过怎么办 墨鱼汁弄衣服上怎么办 干鱿鱼泡开发黄怎么办 吃了芥末胃疼怎么办 手撕鱿鱼咸了怎么办 孕妇吃了点芥末怎么办 葡萄酒上面有一层白霉怎么办 手机一不小心把视频删了怎么办 柑橘7月份果实小怎么办 鹦鹉鱼身上有小白点怎么办 鹦鹉鱼身上烂了怎么办 红鹦鹉鱼变黑了怎么办 唱美声总夹嗓子怎么办 发财树树干出褶怎么办 月季花夏天换盆出现黄叶怎么办 刚种的月季枯了怎么办 新买的月季黄叶了怎么办 月季花叶孑轰发黄怎么办? 新种的月季苗弱怎么办? 月季换盆后浇透水叶子黄了怎么办 肉肉移栽后浇透水怎么办 月季花扦插的没长根发芽了怎么办 君子兰发的小苗怎么办 蔷薇光长枝条不开花怎么办 牡丹发芽又干了怎么办 擦皮炎平后皮肤变黑怎么办 误喝发霉的咖啡渣怎么办 狗吃了速溶咖啡怎么办 咖啡机放豆的地方进水怎么办 干吃咖啡粉上瘾怎么办 去良友花艺住宿怎么办 充气娃娃放了气怎么办 煮杜鹃根没有锅怎么办 淘宝店卖鲜花被买家拒收货怎么办 执业医师电子注册忘记密码怎么办 怀孕吃了油炸的怎么办