1. 程式人生 > >PNP : Remote Procedure Call (RPC)

PNP : Remote Procedure Call (RPC)

RPC

這裡寫圖片描述

RPC是構建分散式系統的基礎。

這裡寫圖片描述
RPC不會限制所使用的語言,跨語言。

這裡寫圖片描述

protobuf提供了message passing的能力,可以在此基礎之上實現一個RPC框架。

type表示訊息型別,是請求還是響應
id用來標識一個請求

service是一個string, 表示客戶端想要呼叫哪個class
method,表示客戶端想要呼叫該class的哪個方法
request 表示請求,即method的引數
response表示響應,即method的返回值

這裡寫程式碼片

伺服器收到請求後,根據message的service名字找到具體的處理類的具體method,然後將request傳遞給該method,並且將response返回。

這樣就避免了手動處理型別,呼叫處理函式的麻煩。下面是實現的虛擬碼:

這裡寫圖片描述

根據service的名字找到對應的類
然後使用該類的GetDescriptor方法得到該類的 Descriptor, 通過這個Descriptor可以知道該類支援哪些方法method(自省)
然後找到method的Descriptor
將message反序列化
根據method的descriptor,建立一個返回型別訊息
最後使用該service的CallMethod執行method

可以看到通過Descriptor,可以通過方法的名字找到對應的方法。確實很方便,不必根據訊息型別收到將請求分發到各個處理函數了。

這裡寫圖片描述

其中CallMethod為虛擬函式,Service為SudokuService,所以呼叫了SudokuService::CallMethod , CallMethod是對方法的分發,它根據method的Descriptor,得到method的index,從而呼叫響應的函式,這裡是: SudokuService::Solve, Solve也是一個虛擬函式,具體呼叫 SudokuServiceImpl::Solve()

實現分析

主要思想是利用protobuf,實現一些介面(虛擬函式)。因此要弄清楚伺服器需要實現什麼介面,客戶端需要實現什麼介面。

先看伺服器端

這裡寫圖片描述

protobuf會自動生成SudokuService類,它繼承了Service類,該類定義了兩個virtual function: GetDescriptor 和 CallMethod

其中函式Solve就是proto檔案描述的服務。

SudokuService會有兩個派生類

  • SudokuService_Stub :客戶端
  • SudokuServiceImpl : 伺服器端

其中Solve函式是需要我們自己具體實現的,那麼Solve是在哪裡被呼叫的呢?答案是CallMethod

這裡寫圖片描述

這些都是自動生成的,其中down_cast是進行向下轉型,因為引數request是一個基類的指標,需要轉換為具體的型別。down_cast在Debug模式使用dynamic_cast ,否則使用static_cast. 這樣可以避免dynamic_cast的開銷。

實現服務端的程式碼:
這裡寫圖片描述

繼承SudokuService,然後override 虛擬函式Solve即可。

那麼怎麼應用到muduo中的呢?

這裡寫圖片描述
其中services是service名字到服務的實現對映map, 收到一個完整的RpcMessage時,根據service的名字找到對應的service實現。然後使用GetDescriptor、FindMethodByName得到呼叫的方法。然後反序列化訊息,new一個response,接著呼叫CallMethod,注意傳入的引數done是一個lambda,該函式就是Solve的最後一個引數,上面提到的伺服器實現的Solve函式最後會呼叫done,即這裡的lambda是在伺服器準備好response後呼叫用來生成響應,併發送回客戶端的。此外注意,需要delete request和response。

這裡寫圖片描述

那麼客戶端怎麼實現的呢?

這裡寫圖片描述

客戶端使用的類SudokuService_Stub是自動生成的,其中Solve呼叫的是RpcChannel的CallMethod,此函式是一個純虛擬函式,需要自己實現RpcChannel類時實現該虛擬函式。

客戶端程式碼框架
這裡寫圖片描述

這裡的stub就是SudokuService_Stub。
構造一個RpcChannel,然後對TcpConnection設定回撥函式為RpcChannel::onMessage,這樣RpcChannel就可以拿到訊息了。Channel也儲存一個TcpConnection的指標,這樣就可以用來發送資料了。然後以此Channel建立一個Stub

接著就是構造請求,呼叫stub的Solve,並傳入一個labmda,在收到響應時呼叫。

所以這裡關鍵是如何實現RpcChannel:
這裡寫圖片描述

這裡是非同步的client,需要記住哪些是發出了請求,但是沒有收到響應的,即outstandings。

RpcChannel::CallMethod的實現
這裡寫圖片描述

這裡寫圖片描述

客戶端call trace

列表內容