C++服務端TARS是什么
這篇文章主要介紹“ C++服務端TARS是什么”,在日常操作中,相信很多人在 C++服務端TARS是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答” C++服務端TARS是什么”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
TARS是騰訊使用十年的微服務開發框架,目前支持C++、Java、PHP、Node.js、Go語言。該開源項目為用戶提供了涉及到開發、運維、以及測試的一整套微服務平臺PaaS解決方案,幫助一個產品或者服務快速開發、部署、測試、上線。目前該框架應用在騰訊各大核心業務,基于該框架部署運行的服務節點規模達到數十萬。
TARS的通信模型中包含客戶端和服務端。客戶端服務端之間主要是利用RPC進行通信。本系列文章分上下兩篇,對RPC調用部分進行源碼解析。在使用TARS構建RPC服務端的時候,TARS會幫你生成一個XXXServer類,這個類是繼承自Application類的,聲明變量XXXServer g_app,以及調用函數:便可以開啟TARS的RPC服務了。在開始剖析TARS的服務端代碼之前,先介紹幾個重要的類,讓大家有一個大致的認識。Application
正如前面所言,一個服務端就是一個Application,Application幫助用戶讀取配置文件,根據配置文件初始化代理(假如這個服務端需要調用其他服務,那么就需要初始化代理了)與服務,新建以及啟動網絡線程與業務線程。TC_EpollServer
TC_EpollServer才是真正的服務端,如果把Application比作風扇,那么TC_EpollServer就是那個馬達。TC_EpollServer掌管兩大模塊——網絡模塊與業務模塊,就是下面即將介紹的兩個類。NetThread
代表著網絡模塊,內含TC_Epoller作為IO復用,TC_Socket建立socket連接,ConnectionList記錄眾多對客戶端的socket連接。任何與網絡相關的數據收發都與NetThread有關。在配置文件中,利用/tars/application/server 下的netthread配置NetThread的個數HandleGroup與Handle
代表著業務模塊,Handle是執行PRC服務的一個線程,而眾多Handle組成的HandleGroup就是同一個RPC服務的一組業務線程了。業務線程負責調用用戶定義好的服務代碼,并將處理結果放到發送緩存中等待網絡模塊發送,下文將會詳細講解業務線程如何調用用戶定義的代碼的,這里用到了簡單的C++反射,這點在很多資料中都沒有被提及。在配置文件中,利用/tars/application/server/xxxAdapter 下的threads配置一個HandleGroup中的Handle(業務線程)的個數。BindAdapter
代表一個RPC服務實體,在配置文件中的/tars/application/server下面的xxxAdapter就是對BindAdapter的配置,一個BindAdapter代表一個服務實體,看其配置就知道BindAdapter的作用是什么了,其代表一個RPC服務對外的監聽套接字,還聲明了連接的最大數量,接收隊列的大小,業務線程數,RPC服務名,所使用的協議等。BindAdapter本身可以認為是一個服務的實例,能建立真實存在的監聽socket并對外服務,與網絡模塊NetThread以及業務模塊HandleGroup都有關聯,例如,多個NetThread的第一個線程負責對BindAdapter的listen socket進行監聽,有客戶連接到BindAdapter的listen socket就隨機在多個NetThread中選取一個,將連接放進被選中的NetThread的ConnectionList中。BindAdapter則通常會與一組HandleGroup進行關聯,該HandleGroup里面的業務線程就執行BindAdapter對應的服務。可見,BindAdapter與網絡模塊以及業務模塊都有所關聯。好了,介紹完這幾個類之后,通過類圖看看他們之間的關系:服務端TC_EpollServer管理類圖中左側的網絡模塊與右側的業務模塊,前者負責建立與管理服務端的網絡關系,后者負責執行服務端的業務代碼,兩者通過BindAdapter構成一個整體,對外進行RPC服務。與客戶端一樣,服務端也需要進行初始化,來構建上面所說的整體,按照上面的介紹,可以將初始化分為兩模塊——網絡模塊的初始化與業務模塊的初始化。初始化的所有代碼在Application的void main()以及void waitForQuit()中,初始化包括屏蔽pipe信號,讀取配置文件等,這些將忽略不講,主要看看其如何通過epoll與建立listen socket來構建網絡部分,以及如何設置業務線程組構建業務部分。TC_EpollServer的初始化
在初始化網絡模塊與業務模塊之前,TC_EpollServer需要先初始化,主要代碼在:在initializeServer()中會填充ServerConfig里面的各個靜態成員變量,留待需要的時候取用。可以看到有_epollServer = new TC_EpollServer(iNetThreadNum),服務端TC_EpollServer被創建出來,而且網絡線程NetThread也被建立出來了:此后,其實有一個AdminAdapter被建立,但其與一般的RPC服務BindAdapter不同,這里不展開介紹。
好了,TC_EpollServer被構建之后,如何給他安排左(網絡模塊)右(業務模塊)護法呢?
網絡模塊初始化
在講解網絡模塊之前,再認真地看看網絡模塊的相關類圖:先看看Application中哪些代碼與網絡模塊的初始化有關吧:網絡部分的初始化,離不開建立各RPC服務的監聽端口(socket,bind,listen),接收客戶端的連接(accept),建立epoll等。那么何時何地調用這些函數呢?大致過程如下圖所示:1. 創建服務實體的listen socket
首先在Application::main()中,調用:在Application::bindAdapter()建立一個個服務實體BindAdapter,通過讀取配置文件中的/tars/application/server下面的xxxAdapter來確定服務實體BindAdapter的個數及不同服務實體的配置,然后再調用:來確定服務實體的listen socket。可以看到,在TC_EpollServer::bind()中:將上文TC_EpollServer的初始化時創建的網絡線程組中的第一條網絡線程負責創建并監聽服務實體的listen socket,那樣就可以避免多線程監聽同一個fd的驚群效應。可以看到,接下來繼續調用NetThread::bind(BindAdapterPtr &lsPtr),其負責做一些準備工作,實際創建socket的是在NetThread::bind(BindAdapterPtr &lsPtr)中執行的NetThread::bind(const TC_Endpoint &ep, TC_Socket &s):執行到這里,已經創建了服務實體BindAdapter的listen socket了,代碼退回到NetThread::bind(BindAdapterPtr &lsPtr)后,還可以看到NetThread記錄fd其所負責監聽的BindAdapter:下圖是對創建服務實體的listen socket的流程總結2.創建epoll
代碼回到Application::main()中,通過執行:來讓TC_EpollServer在其掌管的網絡線程中建立epoll:代碼來到NetThread::createEpoll(uint32_t iIndex),這個函數可以作為網絡線程NetThread的初始化函數,在函數里面建立了網絡線程的內存池,創建了epoll,還將上面創建的listen socket加入epoll中,當然只有第一條網絡線程才有listen socket,此外還初始化了連接管理鏈表ConnectionList _list。看下圖對本流程的總結:3.啟動網絡線程
由于NetThread是線程,需要執行其start()函數才能啟動線程。而這個工作不是在Application::main()中完成,而是在Application::waitForShutdown()中的Application::waitForQuit()完成,跟著下面的流程圖看代碼,就清楚明白了:業務模塊的初始化
同樣,與網絡模塊一樣,在講解業務模塊之前,先認真地看看業務模塊的相關類圖:
在業務模塊初始化中,我們需要理清楚兩個問題:業務模塊如何與用戶填充實現的XXXServantImp建立聯系,從而使請求到來的時候,Handle能夠調用用戶定義好的RPC方法?業務線程在何時何地被啟動,如何等待著請求的到達?
看看Application中哪些代碼與業務模塊的初始化有關吧:
在bindAdapter(adapters)與initialize()中解決了前面提到的第一個問題,剩下的代碼實現了handle業務線程組的創建與啟動。1.將BindAdapter與用戶定義的方法關聯起來
如何讓業務線程能夠調用用戶自定義的代碼?這里引入了ServantHelperManager,先簡單劇透一下,通過ServantHelperManager作為橋梁,業務線程可以通過BindAdapter的ID索引到服務ID,然后通過服務ID索引到用戶自定義的XXXServantImp類的生成器,有了生成器,業務線程就可以生成XXXServantImp類并調用里面的方法了。下面一步一步分析。在Application::main()調用的Application::bindAdapter()中看到有下面的代碼:舉個例子,adapterNamei為MyDemo.StringServer.StringServantAdapter,而servant為MyDemo.StringServer.StringServantObj,這些都是在配置文件中讀取的,前者是BindAdapter的ID,而后者是服務ID。在ServantHelperManager:: setAdapterServant()中,僅僅是執行:在這里僅僅是作一個映射記錄,后續可以通過BindAdapter的ID可以索引到服務的ID,通過服務的ID可以利用簡單的C++反射得出用戶實現的XXXServantImp類,從而得到用戶實現的方法。
如何實現從服務ID到類的反射?同樣需要通過ServantHelperManager的幫助。在Application::main()中,執行完Application::bindAdapter()會執行initialize(),這是一個純虛函數,實際會執行派生類XXXServer的函數,類似:
代碼最終會執行ServantHelperManager:: addServant<T>():其中參數const string& id是服務ID,例如上文的MyDemo.StringServer.StringServantObj,T是用戶填充實現的XXXServantImp類。上面代碼的_servant_creatorid = new ServantCreation<T>()是函數的關鍵,_servant_creator是map<string, ServantHelperCreationPtr>,可以通過服務ID索引到ServantHelperCreationPtr,而ServantHelperCreationPtr是什么?是幫助我們生成XXXServantImp實例的類生成器,這就是簡單的C++反射:以上就是通過服務ID生成相應XXXServantImp類的簡單反射技術,業務線程組里面的業務線程只需要獲取到所需執行的業務的BindAdapter的ID,就可以通過ServantHelperManager獲得服務ID,有了服務ID就可以獲取XXXServantImp類的生成器從而生成XXXServantImp類執行里面由用戶定義好的RPC方法。現在重新看圖(2-8)就大致清楚整個流程了。
2.Handle業務線程的啟動
剩下的部分就是HandleGroup的創建,并將其與BindAdapter進行相互綁定關聯,同時也需要綁定到TC_EpollServer中,隨后創建/啟動HandleGroup下面的Handle業務線程,啟動Handle的過程涉及上文“將BindAdapter與用戶定義的方法關聯起來”提到的獲取服務類生成器。先看看大致的代碼流程圖:
在這里分兩部分,第一部分是在Application::main()中執行下列代碼:
遍歷在配置文件中定義好的每一個BindAdapter(例如MyDemo.StringServer.StringServantAdapter),并為其設置業務線程組HandleGroup,讓線程組的所有線程都可以執行該BindAdapter所對應的RPC方法。跟蹤代碼如下:注意,ServantHandle是Handle的派生類,就是業務處理線程類,隨后來到:真正創建業務線程組HandleGroup以及組內的線程,并將線程組與BindAdapter,TC_EpollServer關聯起來的代碼在TC_EpollServer:: setHandleGroup()中:在這里,可以看到業務線程組的創建:HandleGroupPtr hg = new HandleGroup();業務線程的創建:HandlePtr handle = new T()(T是ServantHandle);建立關系,例如BindAdapter與HandleGroup的相互關聯:it->second->adaptersadapter->getName() = adapter和adapter->_handleGroup = it->second。執行完上面的代碼,就可以得到下面的類圖了:這里再通過函數流程圖簡單復習一下上述代碼的流程,主要內容均在TC_EpollServer:: setHandleGroup()中:
隨著函數的層層退出,代碼重新來到Application::main()中,隨后執行:在TC\_EpollServer::startHandle()中,遍歷TC\_EpollServer控制的業務模塊HandleGroup中的所有業務線程組,并遍歷組內的各個Handle,執行其start()方法進行線程的啟動:由于Handle是繼承自TC_Thread的,在執行Handle::start()中,會執行虛函數Handle::run(),在Handle::run()中主要是執行兩個函數,一個是ServantHandle::initialize(),另一個是Handle::handleImp():
ServantHandle::initialize()的主要作用是取得用戶實現的RPC方法,其實現原理與上文(“2.2.3業務模塊的初始化”中的第1小點“將BindAdapter與用戶定義的方法關聯起來”)提及的一樣,借助與其關聯的BindAdapter的ID號,以及ServantHelpManager,來查找到用戶填充實現的XXXServantImp類的生成器并生成XXXServantImp類的實例,將這個實例與服務名構成pair <string, ServantPtr>變量,放進map<string, ServantPtr> ServantHandle:: _servants中,等待業務線程Handle需要執行用戶自定義方法的時候,從map<string, ServantPtr> ServantHandle:: _servants中查找:而Handle::handleImp()的主要作用是使業務線程阻塞在等待在條件變量上,在這里,可以看到_handleGroup->monitor.timedWait(_iWaitTime)函數,阻塞等待在條件變量上:Handle線程通過條件變量來讓所有業務線程阻塞等待被喚醒 ,因為本章是介紹初始化,因此代碼解讀到這里先告一段落,稍后再詳解服務端中的業務線程Handle被喚醒后,如何通過map<string, ServantPtr> ServantHandle:: _servants查找并執行業務。現在通過函數流程圖復習一下上述的代碼流程:經過了初始化工作后,服務端就進入工作狀態了,服務端的工作線程分為兩類,正如前面所介紹的網絡線程與業務線程,網絡線程負責接受客戶端的連接與收發數據,而業務線程則只關注執行用戶所定義的PRC方法,兩種線程在初始化的時候都已經執行start()啟動了。
大部分服務器都是按照accept()->read()->write()->close()的流程執行的,大致工作流程圖如下圖所示:
判定邏輯采用Epoll IO復用模型實現,每一條網絡線程NetThread都有一個TC_Epoller來做事件的收集、偵聽、分發。正如前面所介紹,只有第一條網絡線程會執行連接的監聽工作,接受新的連接之后,就會構造一個Connection實例,并選擇處理這個連接的網絡線程。請求被讀入后,將暫存在接收隊列中,并通知業務線程進行處理,在這里,業務線程終于登場了,處理完請求后,將結果放到發送隊列。發送隊列有數據,自然需要通知網絡線程進行發送,接收到發送通知的網絡線程會將響應發往客戶端。TARS服務器的工作流程大致就是如此,如上圖所示的普通服務器工作流程沒有多大的區別,下面將按著接受客戶端連接,讀入RPC請求,處理RPC請求,發送RPC響應四部分逐一介紹介紹服務端的工作。接受客戶端連接
討論服務器接受請求,很明顯是從網絡線程(而且是網絡線程組的第一條網絡線程)的NetThread::run()開始分析,在上面說到的創建TC_Epoller并將監聽fd放進TC_Epoller的時候,執行的是:那么從epoll_wait()返回的時候,epoll_event中的聯合體epoll_data將會是(ET_LISTEN | listen socket’fd),從中獲取高32位,就是ET_LISTEN,然后執行下面switch中case ET_LISTEN的分支而ret = accept(ev.data.u32)的整個函數流程如下圖所示(ev.data.u32就是被激活的BindAdapter對應的監聽socket的fd):在講解之前,先復習一下網絡線程相關類圖,以及通過圖解對accept有個大致的印象:好了,跟著圖(2-14),現在從NetThread::run()的NetThread::accept(int fd)講起。1.accept 獲取客戶端socket
進入NetThread::accept(int fd),可以看到代碼執行了:通過TC_Socket::accept(),調用系統函數accept()接受了客戶端的辛辛苦苦三次握手來的socket連接,然后對客戶端的IP與端口進行打印以及檢查,并分析對應的BindAdapter是否過載,過載則關閉連接。隨后對客戶端socket進行設置:到此,對應圖(2-16)的第一步——接受客戶端連接(流程如下圖所示),已經完成。2.為客戶端socket創建Connection
接下來是為新來的客戶端socket創建一個Connection,在NetThread::accept(int fd)中,創建Connection的代碼如下:構造函數中的參數依次是,這次新客戶端所對應的BindAdapter指針,BindAdapter對應的listen socket的fd,超時時間,客戶端socket的fd,客戶端的ip以及端口。在Connection的構造函數中,通過fd也關聯其TC_Socket:
那么關聯TC_Socket之后,通過Connection實例就可以操作的客戶端socket了。至此,對應圖(2-16)的第二步——為客戶端socket創建Connection就完成了(流程如下圖所示)。3.為Connection選擇一條網絡線程
最后,就是為這個Connection選擇一個網絡線程,將其加入網絡線程對應的ConnectionList,在NetThread::accept(int fd)中,執行:TC_EpollServer::addConnection()的代碼如下所示:看到,先為Connection* cPtr選擇網絡線程,在流程圖中,被選中的網絡線程稱為Chosen_NetThread。選網絡線程的函數是TC_EpollServer::getNetThreadOfFd(int fd),根據客戶端socket的fd求余數得到,具體代碼如下:接著調用被選中線程的NetThread::addTcpConnection()方法(或者NetThread::addUdpConnection(),這里只介紹TCP的方法),將Connection加入被選中網絡線程的ConnectionList中,最后會執行_epoller.add(cPtr->getfd(), cPtr->getId(), EPOLLIN | EPOLLOUT)將客戶端socket的fd加入本網絡線程的TC_Epoller中,讓本網絡線程負責對本客戶端的數據收發。至此對應圖(28)的第三步就執行完畢了(具體流程如下圖所示)。接收RPC請求
討論服務器接收RPC請求,同樣從網絡線程的NetThread::run()開始分析,上面是進入switch中的case ET_LISTEN分支來接受客戶端的連接,那么現在就是進入case ET_NET分支了,為什么是case ET_NET分支呢?因為上面提到,將客戶端socket的fd加入TC_Epoller來監聽其讀寫,采用的是_epoller.add(cPtr->getfd(), cPtr->getId(), EPOLLIN | EPOLLOUT),傳遞給函數的第二個參數是32位的整形cPtr->getId(),而函數的第二個參數要求必須是64位的整型,因此,這個參數將會是高32位是0,低32位是cPtr->getId()的64位整形。而第二個參數的作用是當該注冊的事件引起epoll_wait()退出的時候,會作為激活事件epoll_event 結構體中的64位聯合體epoll_data_t data返回給用戶。因此,看下面NetThread::run()代碼:代碼中的h是64位聯合體epoll_data_t data的高32位,經過上面分析,客戶端socket若因為接收到數據而引起epoll_wait()退出的話,epoll_data_t data的高32位是0,低32位是cPtr->getId(),因此h將會是0。而ET_NET就是0,因此客戶端socket有數據來到的話,會執行case ET_NET分支。下面看看執行case ET_NET分支的函數流程圖。
1.獲取激活了的連接Connection
收到RPC請求,進入到NetThread::processNet(),服務器需要知道是哪一個客戶端socket被激活了,因此在NetThread::processNet()中執行:正如上面說的,epoll_data_t data的高32位是0,低32位是cPtr->getId(),那么獲取到uid之后,通過NetThread::getConnectionPtr()就可以從ConnectionList中返回此時此刻所需要讀取RPC請求的Connection了。之后對獲取的Connection進行簡單的檢查工作,并看看epoll_event::events是否是EPOLLERR或者EPOLLHUP(具體流程如下圖所示)。2.接收客戶端請求,放進線程安全隊列中
接著,就需要接收客戶端的請求數據了,有數據接收意味著epoll_event::events是EPOLLIN,看下面代碼,主要是NetThread::recvBuffer()讀取RPC請求數據,以及以及Connection:: insertRecvQueue()喚醒業務線程發送數據。先看看NetThread::recvBuffer(),首先服務端會先創建一個線程安全隊列來承載接收到的數據recv_queue::queue_type vRecvData,再將剛剛獲取的Connection cPtr以及recv_queue::queue_type vRecvData作為參數調用NetThread::recvBuffer(cPtr, vRecvData)。而NetThread::recvBuffer()進一步調用Connection::recv()函數:Connection::recv()會依照不同的傳輸層協議(若UDP傳輸,lfd==-1),執行不同的接收方法,例如TCP會執行:根據數據接收情況,如收到FIN分節,errno==EAGAIN等執行不同的動作。若收到真實的請求信息包,會將接收到的數據放在string Connection::_recvbuffer中,然后調用Connection:: parseProtocol()。在Connection:: parseProtocol()中會回調協議解析函數對接收到的數據進行檢驗,檢驗通過后,會構造線程安全隊列中的元素tagRecvData* recv,并將其放進線程安全隊列中:到此,RPC請求數據已經被完全獲取并放置在線程安全隊列中(具體過程如下圖所示)。
3.線程安全隊列非空,喚醒業務線程發送
代碼運行至此,線程安全隊列里面終于有RPC請求包數據了,可以喚醒業務線程Handle進行處理了,代碼回到NetThread::processNet(),只要線程安全隊列非空,就執行Connection:: insertRecvQueue():
在Connection:: insertRecvQueue()中,會先對BindAdapter進行過載判斷,分為未過載,半過載以及全過載三種情況。若全過載會丟棄線程安全隊列中的所有RPC請求數據,否則會執行BindAdapter::insertRecvQueue()。在BindAdapter::insertRecvQueue()中,代碼主要有兩個動作,第一個是將獲取到的RPC請求包放進BindAdapter的接收隊列——recv_queue _rbuffer中:第二個是喚醒等待條件變量的HandleGroup線程組:
現在,服務端的網絡線程在接收RPC請求數據后,終于喚醒了業務線程(具體流程看下圖所示),接下來輪到業務模塊登場,看看如何處理RPC請求了。處理RPC請求
與前文接收到請求數據后,喚醒業務線程組HandleGroup(就是剛剛才介紹完的_handleGroup->monitor.notify())遙相呼應的地方是在“2.2.3業務模塊的初始化”第2小點“Handle業務線程的啟動”中提到的,在Handle::handleImp()函數中的_handleGroup->monitor.timedWait(_iWaitTime)。通過條件變量,業務線程組HandleGroup里面的業務線程一起阻塞等待著網絡線程對其發起喚醒。現在,終于對條件變量發起通知了,接下來將會如何處理請求呢?在這里,需要先對2.2.3節進行復習,了解到ServantHandle::_servants里面究竟承載著什么。好了,處理RPC請求分為三步:構造請求上下文,調用用戶實現的方法處理請求,將響應數據包push到線程安全隊列中并通知網絡線程,具體函數流程如下圖所示,現在進一步分析:1.獲取請求數據構造請求上下文
當業務線程從條件變量上被喚醒之后,從其負責的BindAdapter中獲取請求數據:adapter->waitForRecvQueue(recv, 0),在BindAdapter::waitForRecvQueue()中,將從線程安全隊列recv_queue BindAdapter::_ rbuffer中獲取數據:還記得在哪里將數據壓入線程安全隊列的嗎?對,就在“2.3.2接收RPC請求”的第3點“線程安全隊列非空,喚醒業務線程發送”中。接著,調用ServantHandle::handle()對接收到的RPC請求數據進行處理。處理的第一步正如本節小標題所示——構造請求上下文,用的是ServantHandle::createCurrent():在ServantHandle::createCurrent()中,先new出TarsCurrent實例,然后調用其initialize()方法,在TarsCurrent::initialize(const TC_EpollServer::tagRecvData &stRecvData, int64_t beginTime)中,將RPC請求包的內容放進請求上下文TarsCurrentPtr current中,后續只需關注這個請求上下文即可。另外可以稍微關注一下,若采用TARS協議會使用TarsCurrent::initialize(const string &sRecvBuffer)將請求包的內容放進請求上下文中,否則直接采用memcpy()系統調用來拷貝內容。下面稍微總結一下這小節的流程:2.處理請求(只介紹TARS協議)
本RPC框架支持TARS協議與非TARS協議,下面只會介紹對TARS協議的處理,對于非TARS協議,分析流程也是差不多,對非TARS協議協議感興趣的讀者可以對比著來分析非TARS協議部分。在介紹之前,先看看服務相關的繼承體系,下面不要混淆這三個類了:好了,現在重點放在ServantHandle::handleTarsProtocol(const TarsCurrentPtr ¤t)函數上面。先貼代碼:進入函數中,會先對請求上下文進行預處理,例如set調用合法性檢查,染色處理等。隨后,就依據上下文中的服務名來獲取服務對象:map<string, ServantPtr>::iterator sit = _servants.find(current->getServantName()),_servants在“2.2.3業務模塊的初始化”第2小點“Handle業務線程的啟動”中被賦予內容,其key是服務ID(或者叫服務名),value是用戶實現的服務XXXServantImp實例指針。
隨后就可以利用XXXServantImp實例指針來執行RPC請求了:ret = sit->second->dispatch(current, buffer),在Servant:: dispatch()(如圖(2-26)因為XXXServantImp是繼承自XXXServant,而XXXServant繼承自Servant,所以實際是執行Servant的方法)中,使用不同的協議會有不同的處理方式,這里只介紹TARS協議的,調用了XXXServant::onDispatch(tars::TarsCurrentPtr _current, vector<char> &_sResponseBuffer)方法:
XXXServant類就是執行Tars2Cpp的時候生成的,會依據用戶定義的tars文件來生成相應的純虛函數,以及onDispatch()方法,該方法的動作有:? 1.找出在本服務類中與請求數據相對應的函數;
? 2.解碼請求數據中的函數參數;
? 3.執行XXXServantImp類中用戶定義的相應RPC方法;
? 4.編碼函數執行后的結果;
? 5.return tars::TARSSERVERSUCCESS。
上述步驟是按照默認的服務端自動回復的思路去闡述,在實際中,用戶可以關閉自動回復功能(如:current->setResponse(false)),并自行發送回復(如:servant->async\_response\_XXXAsync(current, ret, rStr))。到此,服務端已經執行了RPC方法,下面稍微總結一下本小節的內容:3.將響應數據包push到線程安全隊列中并通知網絡線程
處理完RPC請求,執行完RPC方法之后,需要將結果(下面代碼中的buffer)回送給客戶端:
由于業務與網絡是獨立開來的,網絡線程收到請求包之后利用條件變量來通知業務線程,而業務線程才有什么方式來通知網絡線程呢?由前面可知,網絡線程是阻塞在epoll中的,因此需要利用epoll來通知網絡線程。這次先看圖解總結,再分析代碼:
在ServantHandle::handleTarsProtocol()中,最后的一步就是回送響應包。數據包的回送經歷的步驟是:編碼響應信息——找出與接收請求信息的網絡線程,因為我們需要通知他來干活——將響應包放進該網絡線程的發送隊列——利用epoll的特性喚醒網絡線程,我們重點看看NetThread::send():到此,服務器中的業務模塊已經完成他的使命,后續將響應數據發給客戶端是網絡模塊的工作了。發送RPC響應
獲取了請求,當然需要回復響應,從上面知道業務模塊通過_epoller.mod(_notify.getfd(), H64(ET_NOTIFY), EPOLLOUT)通知網絡線程的,再加上之前分析“2.3.1接受客戶端請連接”以及“2.3.2接收RPC請求”的經驗,我們知道,這里必須從NetThread::run()開始講起,而且是進入case ET_NOTIFY分支:在NetThread::processPipe()中,先從線程安全隊列中取響應信息包:_sBufQueue.dequeue(sendp, false),這里與“2.3.3處理RPC請求”的第3小點“將響應數據包push到線程安全隊列中并通知網絡線程”遙相呼應。然后從響應信息中取得與請求信息相對應的那個Connection的uid,利用uid獲取Connection:Connection *cPtr = getConnectionPtr(sendp->uid)。由于Connection是聚合了TC_Socket的,后續通過Connection將響應數據回送給客戶端,具體流程如下圖所示:這里用圖解總結一下服務端的工作過程:
TARS可以在考慮到易用性和高性能的同時快速構建系統并自動生成代碼,幫助開發人員和企業以微服務的方式快速構建自己穩定可靠的分布式應用,從而令開發人員只關注業務邏輯,提高運營效率。多語言、敏捷研發、高可用和高效運營的特性使 TARS 成為企業級產品。到此,關于“ C++服務端TARS是什么”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!