跟着Code走,详解Symbian Client/Server架构
(有刪減)
from:http://blog.csdn.net/beyondexisting/article/details/5862363
Client/Server架構(gòu)是Symbian下最主要的進(jìn)程間通信方法。
?===How to use========================
【Server編程代碼】
定義一個(gè)派生自CServer2的類。
實(shí)現(xiàn)必須的NewSessionL純虛函數(shù)。
class CTestServer : public CServer2??
{??
private:??
??? virtual CSession2* NewSessionL(const TVersion& aVersion,const RMessage2& aMessage) const;??
…??
}
初始化過程中,各種初始化操作完成后,必須調(diào)用基類CServer2的StartL("MyTestServer");函數(shù)在內(nèi)核中注冊(cè)Server,傳入的參數(shù)是Server的名字。之后所有的Client嘗試連接時(shí),可以通過這個(gè)名字找到Server。
為了處理Client的請(qǐng)求,Server還必須實(shí)現(xiàn)派生自CSession2的的session類。Server類在NewSessionL函數(shù)中,必須新建session類并返回。Client連接Server時(shí),框架會(huì)自動(dòng)調(diào)用NewSessionL函數(shù),之后Client的所有命令都由新建的session對(duì)象處理。
session對(duì)象必須實(shí)現(xiàn)ServiceL函數(shù),Client發(fā)出請(qǐng)求時(shí),框架會(huì)調(diào)用session對(duì)象的ServiceL函數(shù)進(jìn)行處理。
class CTestSession : public CSession2??
{??
public:???
??? void ServiceL(const RMessage2& aMessage);??
…??
}
【Client編程代碼】
然后再看看Client需要連接Server并請(qǐng)求操作時(shí),需要完成的代碼。Client必須實(shí)現(xiàn)派生自RSessionBase的類,并提供連接Server的函數(shù)和向Server發(fā)送命令的函數(shù)。
class RTestClient : public RSessionBase??
{??
public:??
??? TInt Connect();??
??? TInt SendRequest(TIng arg);??
…??
}
連接Server的函數(shù)可以直接通過調(diào)用基類的CreateSession函數(shù),建立與Server的連接。需要注意的是如果發(fā)現(xiàn)Server程序還沒有啟動(dòng),首先需要把Server程序啟動(dòng)。向Server發(fā)送命令的函數(shù)通過調(diào)用基類的SendReceive函數(shù)實(shí)現(xiàn),這個(gè)函數(shù)的參數(shù)為一個(gè)請(qǐng)求碼和請(qǐng)求參數(shù)。
需要注意的是SendReceive函數(shù)主要有兩個(gè)重載版本,如下。帶TRequestStatus參數(shù)的是異步版本,調(diào)用后該函數(shù)立即返回,請(qǐng)求完成后會(huì)向請(qǐng)求線程發(fā)送RequestComplete的信號(hào)。
void RSessionBase::SendReceive(TInt aFunction,const TIpcArgs& aArgs,TRequestStatus& aStatus) const
TInt RSessionBase::SendReceive(TInt aFunction,const TIpcArgs& aArgs) const
以上就是Client/Server通信的最基本代碼,當(dāng)然其中很多細(xì)節(jié)都沒有包括,你可以查看Nokia的文檔了解詳細(xì)代碼。
?
【Server用戶態(tài)實(shí)現(xiàn)代碼】
Server用戶態(tài)實(shí)現(xiàn)代碼主要包括兩個(gè)部分:一是Server本身的初始化,在內(nèi)核中創(chuàng)建DServer對(duì)象;二是從內(nèi)核中獲取需要處理的Message并進(jìn)行處理。
- 【Server初始化】
CServer2::StartL函數(shù)的實(shí)現(xiàn)代碼在Symbian OS源碼?中的Kernal Package的文件kernel/eka/euser/cbase/ub_svr.cpp中,如下。CServer2有一個(gè)成員變量RServer2 iServer;這個(gè)成員變量保存了當(dāng)前Server在內(nèi)核中對(duì)應(yīng)的DServer對(duì)象的handle。下面代碼中的第一句iServer.CreateGlobal就是創(chuàng)建內(nèi)核DServer對(duì)象。然后會(huì)把自己放入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函數(shù)通過內(nèi)核DServer句柄,調(diào)用異步函數(shù)獲得需要處理的Message,然后調(diào)用SetActive表示AO激活。
EXPORT_C void CServer2::ReStart()??
??? {??
??? iServer.Receive(iMessage,iStatus);??
??? SetActive();??
??? }
- 【從內(nèi)核獲取Message并處理】
當(dāng)從內(nèi)核DServer獲取到需要處理的Message后,void CServer2::RunL()會(huì)被調(diào)用。(如果一直沒有需要處理的Message,那么異步函數(shù)便一直不返回,直到有Message需要處理為止)void CServer2::RunL()根據(jù)Client的請(qǐng)求碼進(jìn)行處理。請(qǐng)注意iMessage也是R類的成員變量,它只是內(nèi)核中message的handle,并不是message本身。
小于0的請(qǐng)求碼是建立連接或者斷開連接的請(qǐng)求。如果是連接請(qǐng)求,內(nèi)核態(tài)會(huì)創(chuàng)建DSession對(duì)象,并放入DServer對(duì)象的session列表中,用戶態(tài)會(huì)調(diào)用NewSessionL函數(shù)創(chuàng)建session對(duì)象,并放入CServer2的session列表中,同時(shí)還會(huì)把用戶態(tài)session對(duì)象的指針保存到內(nèi)核DSession.iSessionCookie中。如果是斷開連接請(qǐng)求,會(huì)用從Message中得到的session對(duì)象指針,complete Client的請(qǐng)求,內(nèi)核態(tài)如果發(fā)現(xiàn)時(shí)EDisconnect請(qǐng)求,會(huì)在真正complete用戶態(tài)Client的請(qǐng)求前,從DServer的session列表中刪除對(duì)應(yīng)的DSession對(duì)象,然后刪除內(nèi)核中的DSession對(duì)象。
大于等于0的請(qǐng)求碼對(duì)應(yīng)Client的其他功能請(qǐng)求。首先從通過message handle從內(nèi)核中獲得對(duì)應(yīng)用戶態(tài)session對(duì)象指針,然后調(diào)用CSession2::ServiceL函數(shù)。
處理完一個(gè)Message后,在void CServer2::RunL()代碼的最后,會(huì)再次調(diào)用ReStart函數(shù)進(jìn)入等待Message狀態(tài)。
?
【Client用戶態(tài)實(shí)現(xiàn)代碼】
Client用戶態(tài)實(shí)現(xiàn)的代碼包括初始創(chuàng)建session連接,后續(xù)的命令發(fā)送操作,及最后的session斷開操作,下面我們分別看。
- 【創(chuàng)建session連接】
創(chuàng)建session連接在用戶編程代碼中通過調(diào)用RSessionBase.CreateSession完成,該函數(shù)調(diào)用Exec::SessionCreate完成session創(chuàng)建。內(nèi)核在ExecHandler::SessionCreate函數(shù)中處理session創(chuàng)建請(qǐng)求,在正式創(chuàng)建session之前,首先會(huì)檢查capability。調(diào)用RSessionBase.CreateSession,僅僅觸發(fā)內(nèi)核創(chuàng)建了DSession對(duì)象,用戶態(tài)Server還并未創(chuàng)建CSession2對(duì)象。RSessionBase.CreateSession會(huì)接著調(diào)用RSessionBase.DoConnect,該函數(shù)向Server發(fā)送RMessage2::EConnect。在前面【Server用戶態(tài)實(shí)現(xiàn)代碼】中已經(jīng)提到,用戶態(tài)如果收到RMessage2::EConnect,會(huì)創(chuàng)建CSession2對(duì)象。
- 【發(fā)送Message到Server】
Client調(diào)用RSessionBase.SendReveive->RSessionBase.DoSendReceive->RSessionBase.SendSync->Exec::SessionSendSync(iHandle,aFunction,(TAny*)aArgs,&s);完成Message發(fā)送。內(nèi)核對(duì)應(yīng)處理函數(shù)為文件kernel/eka/kernel/sipc.cpp中的ExecHandler::SessionSendSync。內(nèi)核首先會(huì)根據(jù)session handle從內(nèi)核對(duì)象列表中找到對(duì)應(yīng)的DSession對(duì)象,然后把Message放入自己的Message隊(duì)列SDblQue?? iMsgQ;,等待用戶態(tài)Server從內(nèi)核獲取Message。如果用戶態(tài)Server已經(jīng)是處于等待Message狀態(tài),內(nèi)核此時(shí)會(huì)complete用戶態(tài)Server獲取Message的異步請(qǐng)求。
- 【斷開session連接】
用戶代碼調(diào)用RSessionBase.Close-> ExecHandler::HandleClose斷開session 連接。內(nèi)核態(tài)實(shí)現(xiàn)代碼是文件kernel/eka/kernel/sexec.cpp中的函數(shù)ExecHandler::HandleClose。該函數(shù)會(huì)從經(jīng)過多個(gè)函數(shù)調(diào)用后,調(diào)用DSession.Close,該函數(shù)把DSession對(duì)象引用計(jì)數(shù)減1,并向向DServer的Message隊(duì)列中加一個(gè)EDisconnect消息。
用戶態(tài)Server對(duì)EDisconnet并沒有多少處理,僅僅立即complete當(dāng)前正在處理的Client請(qǐng)求。內(nèi)核在ExecHandler::MessageComplete中處理complete請(qǐng)求的操作,進(jìn)一步的調(diào)用過程為ExecHandler::MessageComplete->DSession::CloseFromDisconnect->DSession::Detach。DSession::Detach會(huì)complete Dession.iMsgQ中所有pending的request,如果發(fā)現(xiàn)總引用計(jì)數(shù)減為0,會(huì)調(diào)用K::ObjDelete刪除DSession對(duì)象。
NOTE:我并沒有看到CServer2在處理EDisconnect過程中delete CSession2對(duì)象,也沒有看到CServer2在處理新的連接請(qǐng)求時(shí),重用之間new的CSession2,只看到CServer2在析構(gòu)函數(shù)中清理CSession2隊(duì)列,并delete CSession2對(duì)象。不知道是我漏掉了代碼,還是確實(shí)發(fā)現(xiàn)了一個(gè)bug。
?
【內(nèi)核態(tài)實(shí)現(xiàn)代碼】
下面我們?cè)倏纯碨erver在內(nèi)核態(tài)的實(shí)現(xiàn)代碼。
- 【Server創(chuàng)建過程】RServer2.CreateGlobal
首先從Server啟動(dòng)時(shí)調(diào)用iServer.CreateGlobal看起。kernel/eka/euser/cbase/ub_ksvr.cpp中有RServer2.CreateGlobal 實(shí)現(xiàn)代碼,它調(diào)用Exec::ServerCreateWithOptions(&name8, aMode, aRole, aOpts);實(shí)現(xiàn)。根據(jù)??跟著Code走,詳解Symbian OS API?中的介紹,我們可以找到這個(gè)調(diào)用在內(nèi)核中對(duì)應(yīng)的實(shí)現(xiàn)代碼,在Kernel Package中的文件kernel/eka/kernel/sipc.cpp中,其中比較關(guān)鍵的幾句代碼如下。主要的動(dòng)作包括,創(chuàng)建內(nèi)核DServer對(duì)象,設(shè)置DServer的名字,并把創(chuàng)建的對(duì)象放到全局的對(duì)象列表中的EServer類型。
…??
DServer *pS = new DServer;??
…??
r = pS->Create();??
…??
r = pS->SetName(&n);??
…??
r = K::AddObject(pS, EServer);??
…
- 【獲取Message并處理過程】RServer2.Receive
RServer2.Receive的實(shí)現(xiàn)代碼在文件kernel/eka/euser/cbase/us_exec.cpp中,它調(diào)用Exec::ServerReceive(iHandle, aStatus, &aMessage)實(shí)現(xiàn)。其對(duì)應(yīng)的內(nèi)核實(shí)現(xiàn)代碼是文件kernel/eka/kernel/sipc.cpp中的函數(shù)void ExecHandler::ServerReceive(DServer* aServer, TRequestStatus& aStatus, TAny* aMsg),這個(gè)函數(shù)調(diào)用DServer.Receive獲取Message。
DServer.Receive如果發(fā)現(xiàn)當(dāng)前Message隊(duì)列非空,則會(huì)從中獲取一個(gè)Message并準(zhǔn)備返回給用戶態(tài)。如果Message隊(duì)列為空,則不會(huì)complete用戶態(tài)Server取Message的請(qǐng)求,當(dāng)有Client發(fā)送Message到內(nèi)核后,內(nèi)核才會(huì)返回Message給用戶態(tài)Server,并complete用戶態(tài)Server的請(qǐng)求。
if (!iDeliveredQ.IsEmpty())??
??????? {??
??????? RMessageK* m = _LOFF(iDeliveredQ.First()->Deque(), RMessageK, iServerLink);??
??????? Accept(m);??
??????? }
DServer返回Message的代碼中大部分都是為了更新DServer的內(nèi)部狀態(tài),真正把Message相關(guān)數(shù)據(jù)寫到用戶態(tài)空間的過程不太容易看懂。
Accept函數(shù)的最后會(huì)調(diào)用Kern::QueueRequestComplete(iOwningThread, iMessage, KErrNone); –> aRequest->EndComplete(aThread); –> TInt r = NKern::QueueUserModeCallback(&aThread->iNThread, this); 函數(shù)QueueUserModeCallback的實(shí)現(xiàn)代碼如下,它會(huì)把RMessageK對(duì)象賦值給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;??
??? }
這樣當(dāng)OS API調(diào)用返回時(shí),會(huì)有調(diào)用UserModeCallback的動(dòng)作(Symbian OS API調(diào)用有這樣的機(jī)制,OS API調(diào)用完成后會(huì)檢查是否有UserModeCallback需要執(zhí)行,如果有則執(zhí)行。詳細(xì)code請(qǐng)查看文件kernel/eka/kernel/arm/victors.cia中的__ArmVectorSwi函數(shù),請(qǐng)注意UserModeCallback是在內(nèi)核態(tài)執(zhí)行的),這時(shí)會(huì)調(diào)用K::USafeWrite函數(shù),把數(shù)據(jù)寫入到用戶態(tài)空間,詳細(xì)過程請(qǐng)查看代碼—文件kernel/eka/kernel/sipc.cpp中的函數(shù)RMessageK::CallbackFunc。
- 【DSession管理】
DServer對(duì)象創(chuàng)建后,除了在線程中保存其handle外,還會(huì)保存在內(nèi)核全局對(duì)象列表中。這樣有Client請(qǐng)求連接時(shí),才能從內(nèi)核全局對(duì)象列表中找到DServer對(duì)象。相關(guān)代碼是文件kernel/eka/kernel/sipc.cpp中函數(shù)ExecHandler::ServerCreateWithOptions的下面一段。K::AddObject把DServer對(duì)象pS放到內(nèi)核全局對(duì)象列表中的EServer類型中,K::MakeHandle在當(dāng)前線程中保存DServer對(duì)象pS的handle。
??? r = K::AddObject(pS, EServer);??
??????????? if (r == KErrNone)??
??????????????? r = K::MakeHandle(nameLen ? EOwnerThread : EOwnerProcess, pS);??
??????????? }
DSession對(duì)象就不需要全局保存了,只需要保存在線程數(shù)據(jù)中即可。因?yàn)镈Server對(duì)象中已經(jīng)保存了DSession對(duì)象列表,這保證DSession對(duì)象一定可以被找到。文件kernel/eka/kernel/sipc.cpp中函數(shù)ExecHandler::SessionCreate有以下兩句代碼,第一句的作用是把新建的DSession對(duì)象放到DServer的session隊(duì)列(經(jīng)查看代碼,DServer對(duì)象并沒有直接操作DSession,只是保存了DSession指針而已),第二句的作用是把DSession對(duì)象handle保存到線程數(shù)據(jù)中。請(qǐng)注意,session創(chuàng)建是由Client線程發(fā)起的,所以這里是保存在Client線程的內(nèi)核數(shù)據(jù)中。
…??
r = s->Add(svr, aSecurityPolicy);??
…??
if (r==KErrNone)??
??????? r = s->MakeHandle();??
…
Client要發(fā)送請(qǐng)求時(shí),內(nèi)核從當(dāng)前線程內(nèi)核數(shù)據(jù)中可以得到對(duì)應(yīng)DSession對(duì)象的handle。你可以從以下函數(shù)調(diào)用弄清查找DSession對(duì)象的過程。
ExecHandler::SessionSendSync->DSession::SendSync->K::ObjectFromHandle
什么時(shí)候刪除DSession對(duì)象呢?前面【斷開session連接】已經(jīng)說明,用戶代碼調(diào)用RHandleBase.Close后觸發(fā)一系列動(dòng)作。
- 【Message管理】
首先需要知道的是Message是放在內(nèi)核全局?jǐn)?shù)據(jù)K::SMsgInfo K::MsgInfo;中的(內(nèi)核全局?jǐn)?shù)據(jù)都定義在文件kernel/eka/kernel/sglobals.cpp中)。MsgInfo是如下定義的數(shù)據(jù)結(jié)構(gòu),實(shí)際對(duì)應(yīng)一個(gè)內(nèi)存chunk,同時(shí)保存了下一個(gè)可用的message地址,剩余空閑的message空間等。對(duì)MsgInfo本身的操作,都封裝在RMessageK中,無(wú)非是些內(nèi)存操作,此處不贅述。
static struct SMsgInfo??
??? {??
??? DChunk* iChunk;??
??? TUint8* iBase;??
??? TUint iMaxSize;??
??? TUint iCurrSize;??
??? DMutex* iMsgChunkLock;??
??? RMessageK* iNextMessage;??
??? TInt iFreeMessageCount;??
??? } MsgInfo;
?
下面我們?cè)倏纯碝essage的傳遞過程。用戶態(tài)Client調(diào)用RHandleBase.SendAsync發(fā)送Message到內(nèi)核,內(nèi)核的ExecHandler::SessionSend函數(shù)開始處理。Message傳遞其實(shí)就是函數(shù)調(diào)用過程,我們先列出Message傳遞相關(guān)的函數(shù)調(diào)用過程,如下。(這里我們列出的是異步請(qǐng)求的Message傳遞過程,同步請(qǐng)求直接使用了線程數(shù)據(jù)中專為同步請(qǐng)求預(yù)留的變量TheCurrentThread->iSyncMsgPt)
ExecHandler::SessionSend->DSession::Send->DSession::Send->DServer::Deliver->DServer::Accept->Kern::QueueRequestComplete->TClientRequest::EndComplete->NKern::QueueUserModeCallback
RMessageK在第一個(gè)函數(shù)DSession::Send中生成:RMessageK* m = session->GetNextFreeMessage();
第二個(gè)DSession::Send函數(shù)會(huì)把RMessageK放到DSession的iMsgQ列表中。
函數(shù)DServer::Deliver中,如果這時(shí)用戶態(tài)Server處于等待Message狀態(tài),會(huì)調(diào)用DServer::Accept返回Message,否則調(diào)用RMessageK::SetDelivered把Message放到DServer的SDblQue iDeliveredQ;隊(duì)列中。
后面的操作就是把RMessageK放到UserModeCallback隊(duì)列中,OS API調(diào)用返回時(shí)RMessageK::CallbackFunc會(huì)得到調(diào)用。Message數(shù)據(jù)會(huì)被寫入到用戶態(tài)Server進(jìn)程空間。
TClientRequest::EndComplete調(diào)用NKern::QueueUserModeCallback之后,還會(huì)調(diào)用NKern::ThreadRequestSignal(&aThread->iNThread); 這個(gè)函數(shù)把請(qǐng)求線程的iRequestSemaphore置為有效。這樣Client用戶線程的User::WaitForRequest就會(huì)返回,Client得以繼續(xù)執(zhí)行。
?
我們?cè)俳酉聛砜碿omplete請(qǐng)求的過程。用戶態(tài)Server調(diào)用RMessagePtr2.Complete完成請(qǐng)求,內(nèi)核態(tài)的ExecHandler::MessageComplete處理該請(qǐng)求。我們只關(guān)注普通請(qǐng)求,像EConnect和EDisconnect這種特殊請(qǐng)求,暫時(shí)忽略。complete請(qǐng)求的處理過程并不復(fù)雜,其實(shí)與上面介紹的返回Message給用戶態(tài)Server一樣,這里是返回request結(jié)果給用戶態(tài)Client。
ExecHandler::MessageComplete->Kern::QueueRequestComplete->TClientRequest::EndComplete->NKern::QueueUserModeCallback
?
【Client/Server架構(gòu)圖】
綜上所述,用戶態(tài)Server實(shí)際是一個(gè)ActiveObject,Client/Server在用戶態(tài)主要通過內(nèi)核對(duì)象的handle實(shí)現(xiàn)操作,具體通信過程都在內(nèi)核中實(shí)現(xiàn)。Client/Server架構(gòu)在用戶態(tài)/內(nèi)核態(tài)的實(shí)現(xiàn)結(jié)構(gòu)如下圖。其中需要留意一點(diǎn)的是,CServer2對(duì)象中雖然保存了CSession2對(duì)象的列表,但是并未直接使用,當(dāng)CServer2通過RServer2 handle從內(nèi)核中拿到Message需要處理時(shí),從Message中可以得到內(nèi)核保存的session cookie,實(shí)際就是用戶態(tài)CSession2對(duì)象指針。
(摘自Symbian OS Internals 第四章Inter-thread_Communication?)
總結(jié)
以上是生活随笔為你收集整理的跟着Code走,详解Symbian Client/Server架构的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Ubuntu、CentOS、macOS测
- 下一篇: 印象最深刻的三位老师、难忘的往事