C++服務(wù)端TARS是什么

這篇文章主要介紹“ C++服務(wù)端TARS是什么”,在日常操作中,相信很多人在 C++服務(wù)端TARS是什么問題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答” C++服務(wù)端TARS是什么”的疑惑有所幫助!接下來,請(qǐng)跟著小編一起來學(xué)習(xí)吧!

成都創(chuàng)新互聯(lián)自2013年起,是專業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項(xiàng)目成都網(wǎng)站設(shè)計(jì)、成都網(wǎng)站制作網(wǎng)站策劃,項(xiàng)目實(shí)施與項(xiàng)目整合能力。我們以讓每一個(gè)夢(mèng)想脫穎而出為使命,1280元石門做網(wǎng)站,已為上家服務(wù),為石門各地企業(yè)和個(gè)人服務(wù),聯(lián)系電話:028-86922220

TARS是騰訊使用十年的微服務(wù)開發(fā)框架,目前支持C++、Java、PHP、Node.js、Go語言。該開源項(xiàng)目為用戶提供了涉及到開發(fā)、運(yùn)維、以及測(cè)試的一整套微服務(wù)平臺(tái)PaaS解決方案,幫助一個(gè)產(chǎn)品或者服務(wù)快速開發(fā)、部署、測(cè)試、上線。目前該框架應(yīng)用在騰訊各大核心業(yè)務(wù),基于該框架部署運(yùn)行的服務(wù)節(jié)點(diǎn)規(guī)模達(dá)到數(shù)十萬。  
TARS的通信模型中包含客戶端和服務(wù)端??蛻舳朔?wù)端之間主要是利用RPC進(jìn)行通信。本系列文章分上下兩篇,對(duì)RPC調(diào)用部分進(jìn)行源碼解析。

C++服務(wù)端TARS是什么

在使用TARS構(gòu)建RPC服務(wù)端的時(shí)候,TARS會(huì)幫你生成一個(gè)XXXServer類,這個(gè)類是繼承自Application類的,聲明變量XXXServer g_app,以及調(diào)用函數(shù):

C++服務(wù)端TARS是什么

便可以開啟TARS的RPC服務(wù)了。在開始剖析TARS的服務(wù)端代碼之前,先介紹幾個(gè)重要的類,讓大家有一個(gè)大致的認(rèn)識(shí)。

Application

正如前面所言,一個(gè)服務(wù)端就是一個(gè)Application,Application幫助用戶讀取配置文件,根據(jù)配置文件初始化代理(假如這個(gè)服務(wù)端需要調(diào)用其他服務(wù),那么就需要初始化代理了)與服務(wù),新建以及啟動(dòng)網(wǎng)絡(luò)線程與業(yè)務(wù)線程。

TC_EpollServer

TC_EpollServer才是真正的服務(wù)端,如果把Application比作風(fēng)扇,那么TC_EpollServer就是那個(gè)馬達(dá)。TC_EpollServer掌管兩大模塊——網(wǎng)絡(luò)模塊與業(yè)務(wù)模塊,就是下面即將介紹的兩個(gè)類。

NetThread

代表著網(wǎng)絡(luò)模塊,內(nèi)含TC_Epoller作為IO復(fù)用,TC_Socket建立socket連接,ConnectionList記錄眾多對(duì)客戶端的socket連接。任何與網(wǎng)絡(luò)相關(guān)的數(shù)據(jù)收發(fā)都與NetThread有關(guān)。在配置文件中,利用/tars/application/server 下的netthread配置NetThread的個(gè)數(shù)

HandleGroup與Handle

代表著業(yè)務(wù)模塊,Handle是執(zhí)行PRC服務(wù)的一個(gè)線程,而眾多Handle組成的HandleGroup就是同一個(gè)RPC服務(wù)的一組業(yè)務(wù)線程了。業(yè)務(wù)線程負(fù)責(zé)調(diào)用用戶定義好的服務(wù)代碼,并將處理結(jié)果放到發(fā)送緩存中等待網(wǎng)絡(luò)模塊發(fā)送,下文將會(huì)詳細(xì)講解業(yè)務(wù)線程如何調(diào)用用戶定義的代碼的,這里用到了簡(jiǎn)單的C++反射,這點(diǎn)在很多資料中都沒有被提及。在配置文件中,利用/tars/application/server/xxxAdapter 下的threads配置一個(gè)HandleGroup中的Handle(業(yè)務(wù)線程)的個(gè)數(shù)。

BindAdapter

代表一個(gè)RPC服務(wù)實(shí)體,在配置文件中的/tars/application/server下面的xxxAdapter就是對(duì)BindAdapter的配置,一個(gè)BindAdapter代表一個(gè)服務(wù)實(shí)體,看其配置就知道BindAdapter的作用是什么了,其代表一個(gè)RPC服務(wù)對(duì)外的監(jiān)聽套接字,還聲明了連接的最大數(shù)量,接收隊(duì)列的大小,業(yè)務(wù)線程數(shù),RPC服務(wù)名,所使用的協(xié)議等。
BindAdapter本身可以認(rèn)為是一個(gè)服務(wù)的實(shí)例,能建立真實(shí)存在的監(jiān)聽socket并對(duì)外服務(wù),與網(wǎng)絡(luò)模塊NetThread以及業(yè)務(wù)模塊HandleGroup都有關(guān)聯(lián),例如,多個(gè)NetThread的第一個(gè)線程負(fù)責(zé)對(duì)BindAdapter的listen socket進(jìn)行監(jiān)聽,有客戶連接到BindAdapter的listen socket就隨機(jī)在多個(gè)NetThread中選取一個(gè),將連接放進(jìn)被選中的NetThread的ConnectionList中。BindAdapter則通常會(huì)與一組HandleGroup進(jìn)行關(guān)聯(lián),該HandleGroup里面的業(yè)務(wù)線程就執(zhí)行BindAdapter對(duì)應(yīng)的服務(wù)。可見,BindAdapter與網(wǎng)絡(luò)模塊以及業(yè)務(wù)模塊都有所關(guān)聯(lián)。
好了,介紹完這幾個(gè)類之后,通過類圖看看他們之間的關(guān)系:

C++服務(wù)端TARS是什么

服務(wù)端TC_EpollServer管理類圖中左側(cè)的網(wǎng)絡(luò)模塊與右側(cè)的業(yè)務(wù)模塊,前者負(fù)責(zé)建立與管理服務(wù)端的網(wǎng)絡(luò)關(guān)系,后者負(fù)責(zé)執(zhí)行服務(wù)端的業(yè)務(wù)代碼,兩者通過BindAdapter構(gòu)成一個(gè)整體,對(duì)外進(jìn)行RPC服務(wù)。

C++服務(wù)端TARS是什么

與客戶端一樣,服務(wù)端也需要進(jìn)行初始化,來構(gòu)建上面所說的整體,按照上面的介紹,可以將初始化分為兩模塊——網(wǎng)絡(luò)模塊的初始化與業(yè)務(wù)模塊的初始化。初始化的所有代碼在Application的void main()以及void waitForQuit()中,初始化包括屏蔽pipe信號(hào),讀取配置文件等,這些將忽略不講,主要看看其如何通過epoll與建立listen socket來構(gòu)建網(wǎng)絡(luò)部分,以及如何設(shè)置業(yè)務(wù)線程組構(gòu)建業(yè)務(wù)部分。

TC_EpollServer的初始化

在初始化網(wǎng)絡(luò)模塊與業(yè)務(wù)模塊之前,TC_EpollServer需要先初始化,主要代碼在:

C++服務(wù)端TARS是什么

在initializeServer()中會(huì)填充ServerConfig里面的各個(gè)靜態(tài)成員變量,留待需要的時(shí)候取用。可以看到有_epollServer = new TC_EpollServer(iNetThreadNum),服務(wù)端TC_EpollServer被創(chuàng)建出來,而且網(wǎng)絡(luò)線程N(yùn)etThread也被建立出來了:

C++服務(wù)端TARS是什么

此后,其實(shí)有一個(gè)AdminAdapter被建立,但其與一般的RPC服務(wù)BindAdapter不同,這里不展開介紹。

好了,TC_EpollServer被構(gòu)建之后,如何給他安排左(網(wǎng)絡(luò)模塊)右(業(yè)務(wù)模塊)護(hù)法呢?

網(wǎng)絡(luò)模塊初始化

在講解網(wǎng)絡(luò)模塊之前,再認(rèn)真地看看網(wǎng)絡(luò)模塊的相關(guān)類圖:

C++服務(wù)端TARS是什么

先看看Application中哪些代碼與網(wǎng)絡(luò)模塊的初始化有關(guān)吧:

C++服務(wù)端TARS是什么

網(wǎng)絡(luò)部分的初始化,離不開建立各RPC服務(wù)的監(jiān)聽端口(socket,bind,listen),接收客戶端的連接(accept),建立epoll等。那么何時(shí)何地調(diào)用這些函數(shù)呢?大致過程如下圖所示:

C++服務(wù)端TARS是什么

1. 創(chuàng)建服務(wù)實(shí)體的listen socket
首先在Application::main()中,調(diào)用:

C++服務(wù)端TARS是什么

在Application::bindAdapter()建立一個(gè)個(gè)服務(wù)實(shí)體BindAdapter,通過讀取配置文件中的/tars/application/server下面的xxxAdapter來確定服務(wù)實(shí)體BindAdapter的個(gè)數(shù)及不同服務(wù)實(shí)體的配置,然后再調(diào)用:

C++服務(wù)端TARS是什么

來確定服務(wù)實(shí)體的listen socket??梢钥吹?,在TC_EpollServer::bind()中:

C++服務(wù)端TARS是什么

將上文TC_EpollServer的初始化時(shí)創(chuàng)建的網(wǎng)絡(luò)線程組中的第一條網(wǎng)絡(luò)線程負(fù)責(zé)創(chuàng)建并監(jiān)聽服務(wù)實(shí)體的listen socket,那樣就可以避免多線程監(jiān)聽同一個(gè)fd的驚群效應(yīng)。
可以看到,接下來繼續(xù)調(diào)用NetThread::bind(BindAdapterPtr &lsPtr),其負(fù)責(zé)做一些準(zhǔn)備工作,實(shí)際創(chuàng)建socket的是在NetThread::bind(BindAdapterPtr &lsPtr)中執(zhí)行的NetThread::bind(const TC_Endpoint &ep, TC_Socket &s):

C++服務(wù)端TARS是什么

執(zhí)行到這里,已經(jīng)創(chuàng)建了服務(wù)實(shí)體BindAdapter的listen socket了,代碼退回到NetThread::bind(BindAdapterPtr &lsPtr)后,還可以看到NetThread記錄fd其所負(fù)責(zé)監(jiān)聽的BindAdapter:

C++服務(wù)端TARS是什么

下圖是對(duì)創(chuàng)建服務(wù)實(shí)體的listen socket的流程總結(jié)

C++服務(wù)端TARS是什么

2.創(chuàng)建epoll
代碼回到Application::main()中,通過執(zhí)行:

C++服務(wù)端TARS是什么

來讓TC_EpollServer在其掌管的網(wǎng)絡(luò)線程中建立epoll:

C++服務(wù)端TARS是什么

代碼來到NetThread::createEpoll(uint32_t iIndex),這個(gè)函數(shù)可以作為網(wǎng)絡(luò)線程N(yùn)etThread的初始化函數(shù),在函數(shù)里面建立了網(wǎng)絡(luò)線程的內(nèi)存池,創(chuàng)建了epoll,還將上面創(chuàng)建的listen socket加入epoll中,當(dāng)然只有第一條網(wǎng)絡(luò)線程才有l(wèi)isten socket,此外還初始化了連接管理鏈表ConnectionList _list。看下圖對(duì)本流程的總結(jié):

C++服務(wù)端TARS是什么

3.啟動(dòng)網(wǎng)絡(luò)線程
由于NetThread是線程,需要執(zhí)行其start()函數(shù)才能啟動(dòng)線程。而這個(gè)工作不是在Application::main()中完成,而是在Application::waitForShutdown()中的Application::waitForQuit()完成,跟著下面的流程圖看代碼,就清楚明白了:

C++服務(wù)端TARS是什么

業(yè)務(wù)模塊的初始化

同樣,與網(wǎng)絡(luò)模塊一樣,在講解業(yè)務(wù)模塊之前,先認(rèn)真地看看業(yè)務(wù)模塊的相關(guān)類圖:  

C++服務(wù)端TARS是什么

在業(yè)務(wù)模塊初始化中,我們需要理清楚兩個(gè)問題:業(yè)務(wù)模塊如何與用戶填充實(shí)現(xiàn)的XXXServantImp建立聯(lián)系,從而使請(qǐng)求到來的時(shí)候,Handle能夠調(diào)用用戶定義好的RPC方法?業(yè)務(wù)線程在何時(shí)何地被啟動(dòng),如何等待著請(qǐng)求的到達(dá)?

看看Application中哪些代碼與業(yè)務(wù)模塊的初始化有關(guān)吧:

C++服務(wù)端TARS是什么

在bindAdapter(adapters)與initialize()中解決了前面提到的第一個(gè)問題,剩下的代碼實(shí)現(xiàn)了handle業(yè)務(wù)線程組的創(chuàng)建與啟動(dòng)。
1.將BindAdapter與用戶定義的方法關(guān)聯(lián)起來
如何進(jìn)行關(guān)聯(lián)?先看看下面的代碼流程圖:

C++服務(wù)端TARS是什么

如何讓業(yè)務(wù)線程能夠調(diào)用用戶自定義的代碼?這里引入了ServantHelperManager,先簡(jiǎn)單劇透一下,通過ServantHelperManager作為橋梁,業(yè)務(wù)線程可以通過BindAdapter的ID索引到服務(wù)ID,然后通過服務(wù)ID索引到用戶自定義的XXXServantImp類的生成器,有了生成器,業(yè)務(wù)線程就可以生成XXXServantImp類并調(diào)用里面的方法了。下面一步一步分析。
在Application::main()調(diào)用的Application::bindAdapter()中看到有下面的代碼:

C++服務(wù)端TARS是什么

舉個(gè)例子,adapterNamei為MyDemo.StringServer.StringServantAdapter,而servant為MyDemo.StringServer.StringServantObj,這些都是在配置文件中讀取的,前者是BindAdapter的ID,而后者是服務(wù)ID。在ServantHelperManager:: setAdapterServant()中,僅僅是執(zhí)行:

C++服務(wù)端TARS是什么

而這兩個(gè)成員變量僅僅是:

C++服務(wù)端TARS是什么

在這里僅僅是作一個(gè)映射記錄,后續(xù)可以通過BindAdapter的ID可以索引到服務(wù)的ID,通過服務(wù)的ID可以利用簡(jiǎn)單的C++反射得出用戶實(shí)現(xiàn)的XXXServantImp類,從而得到用戶實(shí)現(xiàn)的方法。

如何實(shí)現(xiàn)從服務(wù)ID到類的反射?同樣需要通過ServantHelperManager的幫助。在Application::main()中,執(zhí)行完Application::bindAdapter()會(huì)執(zhí)行initialize(),這是一個(gè)純虛函數(shù),實(shí)際會(huì)執(zhí)行派生類XXXServer的函數(shù),類似:

C++服務(wù)端TARS是什么

代碼最終會(huì)執(zhí)行ServantHelperManager:: addServant<T>():

C++服務(wù)端TARS是什么

其中參數(shù)const string& id是服務(wù)ID,例如上文的MyDemo.StringServer.StringServantObj,T是用戶填充實(shí)現(xiàn)的XXXServantImp類。
上面代碼的_servant_creatorid = new ServantCreation<T>()是函數(shù)的關(guān)鍵,_servant_creator是map<string, ServantHelperCreationPtr>,可以通過服務(wù)ID索引到ServantHelperCreationPtr,而ServantHelperCreationPtr是什么?是幫助我們生成XXXServantImp實(shí)例的類生成器,這就是簡(jiǎn)單的C++反射:

C++服務(wù)端TARS是什么

以上就是通過服務(wù)ID生成相應(yīng)XXXServantImp類的簡(jiǎn)單反射技術(shù),業(yè)務(wù)線程組里面的業(yè)務(wù)線程只需要獲取到所需執(zhí)行的業(yè)務(wù)的BindAdapter的ID,就可以通過ServantHelperManager獲得服務(wù)ID,有了服務(wù)ID就可以獲取XXXServantImp類的生成器從而生成XXXServantImp類執(zhí)行里面由用戶定義好的RPC方法?,F(xiàn)在重新看圖(2-8)就大致清楚整個(gè)流程了。

2.Handle業(yè)務(wù)線程的啟動(dòng)

剩下的部分就是HandleGroup的創(chuàng)建,并將其與BindAdapter進(jìn)行相互綁定關(guān)聯(lián),同時(shí)也需要綁定到TC_EpollServer中,隨后創(chuàng)建/啟動(dòng)HandleGroup下面的Handle業(yè)務(wù)線程,啟動(dòng)Handle的過程涉及上文“將BindAdapter與用戶定義的方法關(guān)聯(lián)起來”提到的獲取服務(wù)類生成器。先看看大致的代碼流程圖:

C++服務(wù)端TARS是什么

在這里分兩部分,第一部分是在Application::main()中執(zhí)行下列代碼:

C++服務(wù)端TARS是什么

遍歷在配置文件中定義好的每一個(gè)BindAdapter(例如MyDemo.StringServer.StringServantAdapter),并為其設(shè)置業(yè)務(wù)線程組HandleGroup,讓線程組的所有線程都可以執(zhí)行該BindAdapter所對(duì)應(yīng)的RPC方法。跟蹤代碼如下:

C++服務(wù)端TARS是什么

注意,ServantHandle是Handle的派生類,就是業(yè)務(wù)處理線程類,隨后來到:

C++服務(wù)端TARS是什么

真正創(chuàng)建業(yè)務(wù)線程組HandleGroup以及組內(nèi)的線程,并將線程組與BindAdapter,TC_EpollServer關(guān)聯(lián)起來的代碼在TC_EpollServer:: setHandleGroup()中:

C++服務(wù)端TARS是什么

在這里,可以看到業(yè)務(wù)線程組的創(chuàng)建:HandleGroupPtr hg = new HandleGroup();業(yè)務(wù)線程的創(chuàng)建:HandlePtr handle = new T()(T是ServantHandle);建立關(guān)系,例如BindAdapter與HandleGroup的相互關(guān)聯(lián):it->second->adaptersadapter->getName() = adapter和adapter->_handleGroup = it->second。執(zhí)行完上面的代碼,就可以得到下面的類圖了:

C++服務(wù)端TARS是什么

這里再通過函數(shù)流程圖簡(jiǎn)單復(fù)習(xí)一下上述代碼的流程,主要內(nèi)容均在TC_EpollServer:: setHandleGroup()中:

C++服務(wù)端TARS是什么

隨著函數(shù)的層層退出,代碼重新來到Application::main()中,隨后執(zhí)行:

C++服務(wù)端TARS是什么

在TC\_EpollServer::startHandle()中,遍歷TC\_EpollServer控制的業(yè)務(wù)模塊HandleGroup中的所有業(yè)務(wù)線程組,并遍歷組內(nèi)的各個(gè)Handle,執(zhí)行其start()方法進(jìn)行線程的啟動(dòng):

C++服務(wù)端TARS是什么

由于Handle是繼承自TC_Thread的,在執(zhí)行Handle::start()中,會(huì)執(zhí)行虛函數(shù)Handle::run(),在Handle::run()中主要是執(zhí)行兩個(gè)函數(shù),一個(gè)是ServantHandle::initialize(),另一個(gè)是Handle::handleImp():

C++服務(wù)端TARS是什么

ServantHandle::initialize()的主要作用是取得用戶實(shí)現(xiàn)的RPC方法,其實(shí)現(xiàn)原理與上文(“2.2.3業(yè)務(wù)模塊的初始化”中的第1小點(diǎn)“將BindAdapter與用戶定義的方法關(guān)聯(lián)起來”)提及的一樣,借助與其關(guān)聯(lián)的BindAdapter的ID號(hào),以及ServantHelpManager,來查找到用戶填充實(shí)現(xiàn)的XXXServantImp類的生成器并生成XXXServantImp類的實(shí)例,將這個(gè)實(shí)例與服務(wù)名構(gòu)成pair <string, ServantPtr>變量,放進(jìn)map<string, ServantPtr> ServantHandle:: _servants中,等待業(yè)務(wù)線程Handle需要執(zhí)行用戶自定義方法的時(shí)候,從map<string, ServantPtr> ServantHandle:: _servants中查找:

C++服務(wù)端TARS是什么

而Handle::handleImp()的主要作用是使業(yè)務(wù)線程阻塞在等待在條件變量上,在這里,可以看到_handleGroup->monitor.timedWait(_iWaitTime)函數(shù),阻塞等待在條件變量上:

C++服務(wù)端TARS是什么

Handle線程通過條件變量來讓所有業(yè)務(wù)線程阻塞等待被喚醒 ,因?yàn)楸菊率墙榻B初始化,因此代碼解讀到這里先告一段落,稍后再詳解服務(wù)端中的業(yè)務(wù)線程Handle被喚醒后,如何通過map<string, ServantPtr> ServantHandle:: _servants查找并執(zhí)行業(yè)務(wù)?,F(xiàn)在通過函數(shù)流程圖復(fù)習(xí)一下上述的代碼流程:

C++服務(wù)端TARS是什么

C++服務(wù)端TARS是什么

經(jīng)過了初始化工作后,服務(wù)端就進(jìn)入工作狀態(tài)了,服務(wù)端的工作線程分為兩類,正如前面所介紹的網(wǎng)絡(luò)線程與業(yè)務(wù)線程,網(wǎng)絡(luò)線程負(fù)責(zé)接受客戶端的連接與收發(fā)數(shù)據(jù),而業(yè)務(wù)線程則只關(guān)注執(zhí)行用戶所定義的PRC方法,兩種線程在初始化的時(shí)候都已經(jīng)執(zhí)行start()啟動(dòng)了。

大部分服務(wù)器都是按照accept()->read()->write()->close()的流程執(zhí)行的,大致工作流程圖如下圖所示:

C++服務(wù)端TARS是什么

TARS的服務(wù)端也不例外。
判定邏輯采用Epoll IO復(fù)用模型實(shí)現(xiàn),每一條網(wǎng)絡(luò)線程N(yùn)etThread都有一個(gè)TC_Epoller來做事件的收集、偵聽、分發(fā)。
正如前面所介紹,只有第一條網(wǎng)絡(luò)線程會(huì)執(zhí)行連接的監(jiān)聽工作,接受新的連接之后,就會(huì)構(gòu)造一個(gè)Connection實(shí)例,并選擇處理這個(gè)連接的網(wǎng)絡(luò)線程。
請(qǐng)求被讀入后,將暫存在接收隊(duì)列中,并通知業(yè)務(wù)線程進(jìn)行處理,在這里,業(yè)務(wù)線程終于登場(chǎng)了,處理完請(qǐng)求后,將結(jié)果放到發(fā)送隊(duì)列。
發(fā)送隊(duì)列有數(shù)據(jù),自然需要通知網(wǎng)絡(luò)線程進(jìn)行發(fā)送,接收到發(fā)送通知的網(wǎng)絡(luò)線程會(huì)將響應(yīng)發(fā)往客戶端。
TARS服務(wù)器的工作流程大致就是如此,如上圖所示的普通服務(wù)器工作流程沒有多大的區(qū)別,下面將按著接受客戶端連接,讀入RPC請(qǐng)求,處理RPC請(qǐng)求,發(fā)送RPC響應(yīng)四部分逐一介紹介紹服務(wù)端的工作。

接受客戶端連接

討論服務(wù)器接受請(qǐng)求,很明顯是從網(wǎng)絡(luò)線程(而且是網(wǎng)絡(luò)線程組的第一條網(wǎng)絡(luò)線程)的NetThread::run()開始分析,在上面說到的創(chuàng)建TC_Epoller并將監(jiān)聽fd放進(jìn)TC_Epoller的時(shí)候,執(zhí)行的是:

C++服務(wù)端TARS是什么

那么從epoll_wait()返回的時(shí)候,epoll_event中的聯(lián)合體epoll_data將會(huì)是(ET_LISTEN | listen socket’fd),從中獲取高32位,就是ET_LISTEN,然后執(zhí)行下面switch中case ET_LISTEN的分支

C++服務(wù)端TARS是什么

而ret = accept(ev.data.u32)的整個(gè)函數(shù)流程如下圖所示(ev.data.u32就是被激活的BindAdapter對(duì)應(yīng)的監(jiān)聽socket的fd):

C++服務(wù)端TARS是什么

在講解之前,先復(fù)習(xí)一下網(wǎng)絡(luò)線程相關(guān)類圖,以及通過圖解對(duì)accept有個(gè)大致的印象:

C++服務(wù)端TARS是什么

C++服務(wù)端TARS是什么

好了,跟著圖(2-14),現(xiàn)在從NetThread::run()的NetThread::accept(int fd)講起。
1.accept 獲取客戶端socket
進(jìn)入NetThread::accept(int fd),可以看到代碼執(zhí)行了:

C++服務(wù)端TARS是什么

通過TC_Socket::accept(),調(diào)用系統(tǒng)函數(shù)accept()接受了客戶端的辛辛苦苦三次握手來的socket連接,然后對(duì)客戶端的IP與端口進(jìn)行打印以及檢查,并分析對(duì)應(yīng)的BindAdapter是否過載,過載則關(guān)閉連接。隨后對(duì)客戶端socket進(jìn)行設(shè)置:

C++服務(wù)端TARS是什么

到此,對(duì)應(yīng)圖(2-16)的第一步——接受客戶端連接(流程如下圖所示),已經(jīng)完成。

C++服務(wù)端TARS是什么

2.為客戶端socket創(chuàng)建Connection
接下來是為新來的客戶端socket創(chuàng)建一個(gè)Connection,在NetThread::accept(int fd)中,創(chuàng)建Connection的代碼如下:

C++服務(wù)端TARS是什么

構(gòu)造函數(shù)中的參數(shù)依次是,這次新客戶端所對(duì)應(yīng)的BindAdapter指針,BindAdapter對(duì)應(yīng)的listen socket的fd,超時(shí)時(shí)間,客戶端socket的fd,客戶端的ip以及端口。在Connection的構(gòu)造函數(shù)中,通過fd也關(guān)聯(lián)其TC_Socket:

C++服務(wù)端TARS是什么

那么關(guān)聯(lián)TC_Socket之后,通過Connection實(shí)例就可以操作的客戶端socket了。至此,對(duì)應(yīng)圖(2-16)的第二步——為客戶端socket創(chuàng)建Connection就完成了(流程如下圖所示)。

C++服務(wù)端TARS是什么

3.為Connection選擇一條網(wǎng)絡(luò)線程
最后,就是為這個(gè)Connection選擇一個(gè)網(wǎng)絡(luò)線程,將其加入網(wǎng)絡(luò)線程對(duì)應(yīng)的ConnectionList,在NetThread::accept(int fd)中,執(zhí)行:

C++服務(wù)端TARS是什么

TC_EpollServer::addConnection()的代碼如下所示:

C++服務(wù)端TARS是什么

看到,先為Connection* cPtr選擇網(wǎng)絡(luò)線程,在流程圖中,被選中的網(wǎng)絡(luò)線程稱為Chosen_NetThread。選網(wǎng)絡(luò)線程的函數(shù)是TC_EpollServer::getNetThreadOfFd(int fd),根據(jù)客戶端socket的fd求余數(shù)得到,具體代碼如下:

C++服務(wù)端TARS是什么

接著調(diào)用被選中線程的NetThread::addTcpConnection()方法(或者
NetThread::addUdpConnection(),這里只介紹TCP的方法),將Connection加入被選中網(wǎng)絡(luò)線程的ConnectionList中,最后會(huì)執(zhí)行_epoller.add(cPtr->getfd(), cPtr->getId(), EPOLLIN | EPOLLOUT)將客戶端socket的fd加入本網(wǎng)絡(luò)線程的TC_Epoller中,讓本網(wǎng)絡(luò)線程負(fù)責(zé)對(duì)本客戶端的數(shù)據(jù)收發(fā)。至此對(duì)應(yīng)圖(28)的第三步就執(zhí)行完畢了(具體流程如下圖所示)。

C++服務(wù)端TARS是什么

接收RPC請(qǐng)求

討論服務(wù)器接收RPC請(qǐng)求,同樣從網(wǎng)絡(luò)線程的NetThread::run()開始分析,上面是進(jìn)入switch中的case ET_LISTEN分支來接受客戶端的連接,那么現(xiàn)在就是進(jìn)入case ET_NET分支了,為什么是case ET_NET分支呢?因?yàn)樯厦嫣岬?,將客戶端socket的fd加入TC_Epoller來監(jiān)聽其讀寫,采用的是_epoller.add(cPtr->getfd(), cPtr->getId(), EPOLLIN | EPOLLOUT),傳遞給函數(shù)的第二個(gè)參數(shù)是32位的整形cPtr->getId(),而函數(shù)的第二個(gè)參數(shù)要求必須是64位的整型,因此,這個(gè)參數(shù)將會(huì)是高32位是0,低32位是cPtr->getId()的64位整形。而第二個(gè)參數(shù)的作用是當(dāng)該注冊(cè)的事件引起epoll_wait()退出的時(shí)候,會(huì)作為激活事件epoll_event 結(jié)構(gòu)體中的64位聯(lián)合體epoll_data_t data返回給用戶。因此,看下面NetThread::run()代碼:

C++服務(wù)端TARS是什么

代碼中的h是64位聯(lián)合體epoll_data_t data的高32位,經(jīng)過上面分析,客戶端socket若因?yàn)榻邮盏綌?shù)據(jù)而引起epoll_wait()退出的話,epoll_data_t data的高32位是0,低32位是cPtr->getId(),因此h將會(huì)是0。而ET_NET就是0,因此客戶端socket有數(shù)據(jù)來到的話,會(huì)執(zhí)行case ET_NET分支。下面看看執(zhí)行case ET_NET分支的函數(shù)流程圖。

C++服務(wù)端TARS是什么

1.獲取激活了的連接Connection
收到RPC請(qǐng)求,進(jìn)入到NetThread::processNet(),服務(wù)器需要知道是哪一個(gè)客戶端socket被激活了,因此在NetThread::processNet()中執(zhí)行:

C++服務(wù)端TARS是什么

正如上面說的,epoll_data_t data的高32位是0,低32位是cPtr->getId(),那么獲取到uid之后,通過NetThread::getConnectionPtr()就可以從ConnectionList中返回此時(shí)此刻所需要讀取RPC請(qǐng)求的Connection了。之后對(duì)獲取的Connection進(jìn)行簡(jiǎn)單的檢查工作,并看看epoll_event::events是否是EPOLLERR或者EPOLLHUP(具體流程如下圖所示)。

C++服務(wù)端TARS是什么

2.接收客戶端請(qǐng)求,放進(jìn)線程安全隊(duì)列中
接著,就需要接收客戶端的請(qǐng)求數(shù)據(jù)了,有數(shù)據(jù)接收意味著epoll_event::events是EPOLLIN,看下面代碼,主要是NetThread::recvBuffer()讀取RPC請(qǐng)求數(shù)據(jù),以及以及Connection:: insertRecvQueue()喚醒業(yè)務(wù)線程發(fā)送數(shù)據(jù)。

C++服務(wù)端TARS是什么

先看看NetThread::recvBuffer(),首先服務(wù)端會(huì)先創(chuàng)建一個(gè)線程安全隊(duì)列來承載接收到的數(shù)據(jù)recv_queue::queue_type vRecvData,再將剛剛獲取的Connection cPtr以及recv_queue::queue_type vRecvData作為參數(shù)調(diào)用NetThread::recvBuffer(cPtr, vRecvData)。
而NetThread::recvBuffer()進(jìn)一步調(diào)用Connection::recv()函數(shù):

C++服務(wù)端TARS是什么

Connection::recv()會(huì)依照不同的傳輸層協(xié)議(若UDP傳輸,lfd==-1),執(zhí)行不同的接收方法,例如TCP會(huì)執(zhí)行:

C++服務(wù)端TARS是什么

根據(jù)數(shù)據(jù)接收情況,如收到FIN分節(jié),errno==EAGAIN等執(zhí)行不同的動(dòng)作。若收到真實(shí)的請(qǐng)求信息包,會(huì)將接收到的數(shù)據(jù)放在string Connection::_recvbuffer中,然后調(diào)用Connection:: parseProtocol()。
在Connection:: parseProtocol()中會(huì)回調(diào)協(xié)議解析函數(shù)對(duì)接收到的數(shù)據(jù)進(jìn)行檢驗(yàn),檢驗(yàn)通過后,會(huì)構(gòu)造線程安全隊(duì)列中的元素tagRecvData* recv,并將其放進(jìn)線程安全隊(duì)列中:

C++服務(wù)端TARS是什么

到此,RPC請(qǐng)求數(shù)據(jù)已經(jīng)被完全獲取并放置在線程安全隊(duì)列中(具體過程如下圖所示)。

C++服務(wù)端TARS是什么

3.線程安全隊(duì)列非空,喚醒業(yè)務(wù)線程發(fā)送

代碼運(yùn)行至此,線程安全隊(duì)列里面終于有RPC請(qǐng)求包數(shù)據(jù)了,可以喚醒業(yè)務(wù)線程Handle進(jìn)行處理了,代碼回到NetThread::processNet(),只要線程安全隊(duì)列非空,就執(zhí)行Connection:: insertRecvQueue():

C++服務(wù)端TARS是什么

在Connection:: insertRecvQueue()中,會(huì)先對(duì)BindAdapter進(jìn)行過載判斷,分為未過載,半過載以及全過載三種情況。若全過載會(huì)丟棄線程安全隊(duì)列中的所有RPC請(qǐng)求數(shù)據(jù),否則會(huì)執(zhí)行BindAdapter::insertRecvQueue()。
在BindAdapter::insertRecvQueue()中,代碼主要有兩個(gè)動(dòng)作,第一個(gè)是將獲取到的RPC請(qǐng)求包放進(jìn)BindAdapter的接收隊(duì)列——recv_queue _rbuffer中:

C++服務(wù)端TARS是什么

第二個(gè)是喚醒等待條件變量的HandleGroup線程組:

C++服務(wù)端TARS是什么

現(xiàn)在,服務(wù)端的網(wǎng)絡(luò)線程在接收RPC請(qǐng)求數(shù)據(jù)后,終于喚醒了業(yè)務(wù)線程(具體流程看下圖所示),接下來輪到業(yè)務(wù)模塊登場(chǎng),看看如何處理RPC請(qǐng)求了。

C++服務(wù)端TARS是什么

處理RPC請(qǐng)求

與前文接收到請(qǐng)求數(shù)據(jù)后,喚醒業(yè)務(wù)線程組HandleGroup(就是剛剛才介紹完的_handleGroup->monitor.notify())遙相呼應(yīng)的地方是在“2.2.3業(yè)務(wù)模塊的初始化”第2小點(diǎn)“Handle業(yè)務(wù)線程的啟動(dòng)”中提到的,在Handle::handleImp()函數(shù)中的_handleGroup->monitor.timedWait(_iWaitTime)。通過條件變量,業(yè)務(wù)線程組HandleGroup里面的業(yè)務(wù)線程一起阻塞等待著網(wǎng)絡(luò)線程對(duì)其發(fā)起喚醒?,F(xiàn)在,終于對(duì)條件變量發(fā)起通知了,接下來將會(huì)如何處理請(qǐng)求呢?在這里,需要先對(duì)2.2.3節(jié)進(jìn)行復(fù)習(xí),了解到ServantHandle::_servants里面究竟承載著什么。
好了,處理RPC請(qǐng)求分為三步:構(gòu)造請(qǐng)求上下文,調(diào)用用戶實(shí)現(xiàn)的方法處理請(qǐng)求,將響應(yīng)數(shù)據(jù)包push到線程安全隊(duì)列中并通知網(wǎng)絡(luò)線程,具體函數(shù)流程如下圖所示,現(xiàn)在進(jìn)一步分析:

C++服務(wù)端TARS是什么

1.獲取請(qǐng)求數(shù)據(jù)構(gòu)造請(qǐng)求上下文
當(dāng)業(yè)務(wù)線程從條件變量上被喚醒之后,從其負(fù)責(zé)的BindAdapter中獲取請(qǐng)求數(shù)據(jù):adapter->waitForRecvQueue(recv, 0),在BindAdapter::waitForRecvQueue()中,將從線程安全隊(duì)列recv_queue BindAdapter::_ rbuffer中獲取數(shù)據(jù):

C++服務(wù)端TARS是什么

還記得在哪里將數(shù)據(jù)壓入線程安全隊(duì)列的嗎?對(duì),就在“2.3.2接收RPC請(qǐng)求”的第3點(diǎn)“線程安全隊(duì)列非空,喚醒業(yè)務(wù)線程發(fā)送”中。
接著,調(diào)用ServantHandle::handle()對(duì)接收到的RPC請(qǐng)求數(shù)據(jù)進(jìn)行處理。
處理的第一步正如本節(jié)小標(biāo)題所示——構(gòu)造請(qǐng)求上下文,用的是ServantHandle::createCurrent():

C++服務(wù)端TARS是什么

在ServantHandle::createCurrent()中,先new出TarsCurrent實(shí)例,然后調(diào)用其initialize()方法,在TarsCurrent::initialize(const TC_EpollServer::tagRecvData &stRecvData, int64_t beginTime)中,將RPC請(qǐng)求包的內(nèi)容放進(jìn)請(qǐng)求上下文TarsCurrentPtr current中,后續(xù)只需關(guān)注這個(gè)請(qǐng)求上下文即可。另外可以稍微關(guān)注一下,若采用TARS協(xié)議會(huì)使用TarsCurrent::initialize(const string &sRecvBuffer)將請(qǐng)求包的內(nèi)容放進(jìn)請(qǐng)求上下文中,否則直接采用memcpy()系統(tǒng)調(diào)用來拷貝內(nèi)容。下面稍微總結(jié)一下這小節(jié)的流程:

C++服務(wù)端TARS是什么

2.處理請(qǐng)求(只介紹TARS協(xié)議)
當(dāng)獲取到請(qǐng)求上下文之后,就需要對(duì)其進(jìn)行處理了。

C++服務(wù)端TARS是什么

本RPC框架支持TARS協(xié)議與非TARS協(xié)議,下面只會(huì)介紹對(duì)TARS協(xié)議的處理,對(duì)于非TARS協(xié)議,分析流程也是差不多,對(duì)非TARS協(xié)議協(xié)議感興趣的讀者可以對(duì)比著來分析非TARS協(xié)議部分。在介紹之前,先看看服務(wù)相關(guān)的繼承體系,下面不要混淆這三個(gè)類了:

C++服務(wù)端TARS是什么

好了,現(xiàn)在重點(diǎn)放在ServantHandle::handleTarsProtocol(const TarsCurrentPtr ¤t)函數(shù)上面。先貼代碼:

C++服務(wù)端TARS是什么

進(jìn)入函數(shù)中,會(huì)先對(duì)請(qǐng)求上下文進(jìn)行預(yù)處理,例如set調(diào)用合法性檢查,染色處理等。隨后,就依據(jù)上下文中的服務(wù)名來獲取服務(wù)對(duì)象:map<string, ServantPtr>::iterator sit = _servants.find(current->getServantName()),_servants在“2.2.3業(yè)務(wù)模塊的初始化”第2小點(diǎn)“Handle業(yè)務(wù)線程的啟動(dòng)”中被賦予內(nèi)容,其key是服務(wù)ID(或者叫服務(wù)名),value是用戶實(shí)現(xiàn)的服務(wù)XXXServantImp實(shí)例指針。

隨后就可以利用XXXServantImp實(shí)例指針來執(zhí)行RPC請(qǐng)求了:ret = sit->second->dispatch(current, buffer),在Servant:: dispatch()(如圖(2-26)因?yàn)閄XXServantImp是繼承自XXXServant,而XXXServant繼承自Servant,所以實(shí)際是執(zhí)行Servant的方法)中,使用不同的協(xié)議會(huì)有不同的處理方式,這里只介紹TARS協(xié)議的,調(diào)用了XXXServant::onDispatch(tars::TarsCurrentPtr _current, vector<char> &_sResponseBuffer)方法:

C++服務(wù)端TARS是什么

XXXServant類就是執(zhí)行Tars2Cpp的時(shí)候生成的,會(huì)依據(jù)用戶定義的tars文件來生成相應(yīng)的純虛函數(shù),以及onDispatch()方法,該方法的動(dòng)作有:

? 1.找出在本服務(wù)類中與請(qǐng)求數(shù)據(jù)相對(duì)應(yīng)的函數(shù);

? 2.解碼請(qǐng)求數(shù)據(jù)中的函數(shù)參數(shù);

? 3.執(zhí)行XXXServantImp類中用戶定義的相應(yīng)RPC方法;

? 4.編碼函數(shù)執(zhí)行后的結(jié)果;

? 5.return tars::TARSSERVERSUCCESS。

上述步驟是按照默認(rèn)的服務(wù)端自動(dòng)回復(fù)的思路去闡述,在實(shí)際中,用戶可以關(guān)閉自動(dòng)回復(fù)功能(如:current->setResponse(false)),并自行發(fā)送回復(fù)(如:servant->async\_response\_XXXAsync(current, ret, rStr))。到此,服務(wù)端已經(jīng)執(zhí)行了RPC方法,下面稍微總結(jié)一下本小節(jié)的內(nèi)容:

C++服務(wù)端TARS是什么

3.將響應(yīng)數(shù)據(jù)包push到線程安全隊(duì)列中并通知網(wǎng)絡(luò)線程

處理完RPC請(qǐng)求,執(zhí)行完RPC方法之后,需要將結(jié)果(下面代碼中的buffer)回送給客戶端:

C++服務(wù)端TARS是什么

由于業(yè)務(wù)與網(wǎng)絡(luò)是獨(dú)立開來的,網(wǎng)絡(luò)線程收到請(qǐng)求包之后利用條件變量來通知業(yè)務(wù)線程,而業(yè)務(wù)線程才有什么方式來通知網(wǎng)絡(luò)線程呢?由前面可知,網(wǎng)絡(luò)線程是阻塞在epoll中的,因此需要利用epoll來通知網(wǎng)絡(luò)線程。這次先看圖解總結(jié),再分析代碼:

C++服務(wù)端TARS是什么

在ServantHandle::handleTarsProtocol()中,最后的一步就是回送響應(yīng)包。數(shù)據(jù)包的回送經(jīng)歷的步驟是:編碼響應(yīng)信息——找出與接收請(qǐng)求信息的網(wǎng)絡(luò)線程,因?yàn)槲覀冃枰ㄖ麃砀苫睢獙㈨憫?yīng)包放進(jìn)該網(wǎng)絡(luò)線程的發(fā)送隊(duì)列——利用epoll的特性喚醒網(wǎng)絡(luò)線程,我們重點(diǎn)看看NetThread::send():

C++服務(wù)端TARS是什么

到此,服務(wù)器中的業(yè)務(wù)模塊已經(jīng)完成他的使命,后續(xù)將響應(yīng)數(shù)據(jù)發(fā)給客戶端是網(wǎng)絡(luò)模塊的工作了。

發(fā)送RPC響應(yīng)

獲取了請(qǐng)求,當(dāng)然需要回復(fù)響應(yīng),從上面知道業(yè)務(wù)模塊通過_epoller.mod(_notify.getfd(), H64(ET_NOTIFY), EPOLLOUT)通知網(wǎng)絡(luò)線程的,再加上之前分析“2.3.1接受客戶端請(qǐng)連接”以及“2.3.2接收RPC請(qǐng)求”的經(jīng)驗(yàn),我們知道,這里必須從NetThread::run()開始講起,而且是進(jìn)入case ET_NOTIFY分支:

C++服務(wù)端TARS是什么

在NetThread::processPipe()中,先從線程安全隊(duì)列中取響應(yīng)信息包:_sBufQueue.dequeue(sendp, false),這里與“2.3.3處理RPC請(qǐng)求”的第3小點(diǎn)“將響應(yīng)數(shù)據(jù)包push到線程安全隊(duì)列中并通知網(wǎng)絡(luò)線程”遙相呼應(yīng)。然后從響應(yīng)信息中取得與請(qǐng)求信息相對(duì)應(yīng)的那個(gè)Connection的uid,利用uid獲取Connection:Connection *cPtr = getConnectionPtr(sendp->uid)。由于Connection是聚合了TC_Socket的,后續(xù)通過Connection將響應(yīng)數(shù)據(jù)回送給客戶端,具體流程如下圖所示:

C++服務(wù)端TARS是什么

C++服務(wù)端TARS是什么

這里用圖解總結(jié)一下服務(wù)端的工作過程:

C++服務(wù)端TARS是什么

TARS可以在考慮到易用性和高性能的同時(shí)快速構(gòu)建系統(tǒng)并自動(dòng)生成代碼,幫助開發(fā)人員和企業(yè)以微服務(wù)的方式快速構(gòu)建自己穩(wěn)定可靠的分布式應(yīng)用,從而令開發(fā)人員只關(guān)注業(yè)務(wù)邏輯,提高運(yùn)營效率。多語言、敏捷研發(fā)、高可用和高效運(yùn)營的特性使 TARS 成為企業(yè)級(jí)產(chǎn)品。

到此,關(guān)于“ C++服務(wù)端TARS是什么”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!

網(wǎng)頁標(biāo)題:C++服務(wù)端TARS是什么
文章起源:http://muchs.cn/article22/pgdojc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供Google、做網(wǎng)站、全網(wǎng)營銷推廣、動(dòng)態(tài)網(wǎng)站ChatGPT、軟件開發(fā)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

商城網(wǎng)站建設(shè)