客戶方像調用本地方法一樣去調用遠程接口方法,RPC 框架提供接口的代理實現(xiàn),實際的調用將委托給代理RpcProxy 。代理封裝調用信息并將調用轉交給RpcInvoker 去實際執(zhí)行。在客戶端的RpcInvoker 通過連接器RpcConnector 去維持與服務端的通道RpcChannel,并使用RpcProtocol 執(zhí)行協(xié)議編碼(encode)并將編碼后的請求消息通過通道發(fā)送給服務方。RPC 服務端接收器 RpcAcceptor 接收客戶端的調用請求,同樣使用RpcProtocol 執(zhí)行協(xié)議解碼(decode)。解碼后的調用信息傳遞給RpcProcessor 去控制處理調用過程,最后再委托調用給RpcInvoker 去實際執(zhí)行并返回調用結果。
protobuf rpc在上面組件中主要扮演RpcProtocol的角色,使得我們省去了協(xié)議的設計,并且protobuf協(xié)議在編碼和空間效率都是上非常高效的,這也是很多公司采用protobuf作為數(shù)據(jù)序列化和通信協(xié)議的原因。同時protobuf rpc定義了一個抽象的rpc框架,如下圖所示:
RpcServiceStub和RpcService類是protobuf編譯器根據(jù)proto定義生成的類,RpcService定義了服務端暴露給客戶端的函數(shù)接口,具體實現(xiàn)需要用戶自己繼承這個類來實現(xiàn)。RpcServiceStub定義了服務端暴露函數(shù)的描述,并將客戶端對RpcServiceStub中函數(shù)的調用統(tǒng)一轉換到調用RpcChannel中的CallMethod方法,CallMethod通過RpcServiceStub傳過來的函數(shù)描述符和函數(shù)參數(shù)對該次rpc調用進行encode,最終通過RpcConnecor發(fā)送給服務方。對方以客戶端相反的過程最終調用RpcSerivice中定義的函數(shù)。事實上,protobuf rpc的框架只是RpcChannel中定義了空的CallMethod,所以具體怎樣進行encode和調用RpcConnector都要自己實現(xiàn)。RpcConnector在protobuf中沒有定義,所以這個完成由用戶自己實現(xiàn),它的作用就是收發(fā)rpc消息包。在服務端,RpcChannel通過調用RpcService中的CallMethod來具體調用RpcService中暴露給客戶端的函數(shù)。
介紹了這么多,對于怎么樣用protobuf rpc來實現(xiàn)一個rpc肯定還是一頭霧水吧,下面就用protobuf rpc來實現(xiàn)一個簡單的python版rpc demo吧。
下面直接給出demo描述PRC的proto文件,至于proto文件的編寫規(guī)則可以參考protobuf官網。
common.proto文件:
package game; message RequestMessage { required string message = 1; } message ResponseMessage { required string message = 1; }
game_service.proto文件:
package game; import "common.proto"; option py_generic_services = true; service GameService { rpc connect_server(RequestMessage) returns(RequestMessage); }
common.proto文件描述了RPC中收發(fā)的消息;game_service.proto描述了服務器導出的connect_server函數(shù),該函數(shù)接受RequestMessage對象作為參數(shù),并返回RequestMessage對象。在使用PRC協(xié)議時,必須加上option py_generic_services = true;可選項,要不然編譯器不會生成包含connect_server函數(shù)的GameService描述。
使用編譯器protoc編譯proto文件,具體命令為:
protoc.exe --python_out=. game_service.proto
編譯后生成的文件為game_service_pb2.py,該文件主要是實現(xiàn)了GameService和GameService_Stub類。GameService_Stub類用于客戶端調用者來調用GameService的服務。
前面已經說了,在客戶端,RpcChannel只實現(xiàn)了一個空的CallMethod,所以需要繼承RpcChannel重新這個函數(shù)來encode消息和發(fā)送消息。在服務端RpcChannel需要調用CallMethod來調用Service中的函數(shù)。具體實現(xiàn)如下:
class MyRpcChannel(service.RpcChannel): def __init__(self, rpc_service, conn): super(MyRpcChannel, self).__init__() self.logger = LogManager.get_logger("MyRpcChannel") def CallMethod(self, method_descriptor, rpc_controller, request, response_class, done): """"protol buffer rpc 需要的函數(shù),用來發(fā)送rpc調用""" self.logger.info('CallMethod') cmd_index = method_descriptor.index assert(cmd_index < 65535) data = request.SerializeToString() total_len = len(data) + 2 self.conn.send_data(''.join([pack('
最后就是繼承GameService,并實現(xiàn)connect_server函數(shù)了。
class GameService(game_service_pb2.GameService): def __init__(self): self.logger = LogManager.get_logger("GameService") def connect_server(self, rpc_controller, request, callback): self.logger.info('%s', request.message)
至于用于網絡收發(fā)消息的RpcConnector,可以使用python的asyncore庫實現(xiàn),具體實現(xiàn)在這就不討論了。
從上面的實現(xiàn)來看,protobuf rpc的實現(xiàn)主要包括編寫proto文件并編譯生成對應的service_pb2文件,繼承RpcChannel并實現(xiàn)CallMethod和調用Service的CallMethod,繼承Service來實現(xiàn)暴露給客戶端的函數(shù)。
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯(lián)系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com