使用python通过protobuf实现rpc

来源:互联网 发布:用户管理系统 java 编辑:程序博客网 时间:2024/05/21 21:46

欢迎转载,转载请注明原文地址:http://blog.csdn.net/majianfei1023/article/details/71628784


网上有很多教程,基本都是c++的,很多还解释的不够清楚,新手没办法通过文章自己实现一个完整的rpc,而且很少有python的完整教程,
所以我从头用一个完整的echo server demo来讲解protobuf rpc的基本原理。


protobuf rpc echo demo 源码:

github: https://github.com/majianfei/protobuf-rpc-echo

不了解protobuf怎么安装及protobuf的基础语法,可以网上查看教程。我就不多说了。


首先 protobuf rpc帮我们做了什么:
google protobuf只负责消息的打包和解包,并不包含RPC的实现,但其包含了RPC的定义。也不包括通讯机制。
所以我们要自己实现通讯机制。


首先我们定义:game_service.proto:

option py_generic_services = true;message RequestMessage{    required string msg = 1;}message ResponseMessage{    required string msg = 1;}service IEchoService{    rpc echo(RequestMessage) returns(ResponseMessage);}

使用 proto --cpp_out=. game_service.proto
会生成 game_service_pb2.py的文件,里面会有两个类,一个 IEchoService,一个IEchoService_stub继承自IEchoService
这两个有什么区别的,一般来说:IEchoService_stub,作为调用方,IEchoService作为被调用方。(假设我们做单向通讯,客户端->服务器)
当调用方调用echo时,自动通过protobuf rpc传输到被调用方,调用被调用方的echo处理逻辑。


过程是这样的:
1.被调用方主动调用echo发送message数据。
2.protobuf rpc会自动调用rpc_channel的CallMethod方法。
3.CallMethod调用具体的通信过程发送数据。(需要我们实现CallMethod和通信过程)
注:通信过程我们要自己实现,这也是protobuf设计的初衷,在最多变的部分(多种多样的网络结构、协议和通信机制)留出足够的空间让程序员可以针对特定场景自己实现,使得protobuf可以应用在更多的场景。
4.被调用方读到数据
5.数据通过protobuf的定义解析成message格式(自己实现)
6.找到指定IEchoService的echo
7.我们在echo里面实现我们的逻辑就好了。


一般而言,会把rpc的客户端->服务器->客户端变成两个单独的过程。


option py_generic_services = true;message Void {}message RequestMessage{    required string msg = 1;}message ResponseMessage{    required string msg = 1;}//客户端发给服务器service IEchoService{    rpc echo(RequestMessage) returns(Void);}//服务器发给客户端service IEchoClient{    rpc echo_reply(ResponseMessage) returns(Void);}


实现通信层
最简单的,我们使用python的asyncore来实现一个简单的通讯层。
包括 TcpConnection,TcpServer,TcpClient,这应该很简单就能理解,具体可以看github上的源码。


RPC客户端需要实现google.protobuf.RpcChannel。主要实现RpcChannel.CallMethod接口。客户端调用任何一个RPC接口,最终都是调用到CallMethod。这个函数的典型实现就是将RPC调用参数序列化,然后投递给网络模块进行发送。

def CallMethod(self, method_descriptor, rpc_controller, request, response_class, done):index = method_descriptor.indexdata = request.SerializeToString()total_len = len(data) + 6self.logger.debug("CallMethod:%d,%d"%(total_len,index))self.conn.send_data(''.join([struct.pack('!ih', total_len, index), data]))


无论是调用端还是被调用端,一个method_descriptor在其所在Service内的index是一致的。因此method_descriptor的部分只需要对其index进行序列化即可。
RPC调用的参数可以直接使用protobuf的SerializeToString()方法进行序列化,进而在接收端通过ParseFromString()方法反序列化。


protobuf的service API在被调用端为我们完成的工作是,当使用合适的method_descriptor和request参数调用IEchoService.CallMethod()时,会自动调用我们对相应方法接口的具体实现。因此在服务端需要做的工作主要由:


接受调用端发来的数据。
对接收到的数据包进行反序列化,解析得到method_descriptor和request参数。
调用EchoService.CallMethod()。


def input_data(self, data):total_len, index = struct.unpack('!ih', data[0:6])self.logger.debug("input_data:%d,%d" % (total_len, index))rpc_service = self.rpc_services_descriptor = rpc_service.GetDescriptor()method = s_descriptor.methods[index]try:request = rpc_service.GetRequestClass(method)()serialized = data[6:total_len]request.ParseFromString(serialized)rpc_service.CallMethod(method, self.rpc_controller, request, None)except Exception, e:self.logger.error("Call rpc method failed!")print "error:",eself.logger.log_last_except()return True


服务端实现RPC接口,继承自IEchoService。

# 被调用方的Service要自己实现具体的rpc处理逻辑class MyEchoService(IEchoService):def echo(self, controller, request, done):rpc_channel = controller.rpc_channelmsg = request.msgresponse = ResponseMessage()response.msg = "echo:"+msgprint "response.msg", response.msg# 此时,服务器是调用方,就调用stub.rpc,客户端时被调用方,实现rpc方法。client_stub = IEchoClient_Stub(rpc_channel)client_stub.echo_reply(controller, response, None)



整个流程就清晰了,
1.实现RpcChannel,主要实现CallMethod使用底层通讯机制(TcpConnection) 把序列化后的数据发送出去,当服务端TcpConnection收到包之后,反序列化(input_data)


我们要继承 EchoService 并实现echo,当客户端调用EchoService_stub.echo时,会调用RpcChannel的CallMethod发送出去,然后接收方TpcConnection.hand_read调用rpc_channel的input_data,我们实现反序列化过程,再调用EchoService
的CallMethod,protobuf会自动调用我们实现的echo方法,然后服务器回消息到客户端时,调用方跟被调用方就反过来了。我们调用方(服务器)使用stub.echo_reply走原来的流程,到客户端(被调用方),实现echo_reply处理逻辑。


有兴趣可以看看github上的源码。
更有兴趣的可以使用protobuf rpc实现一个完整的rpc server,我个人暂时就不完善了。


我目前在实现分布式服务器架构,底层使用libev网络库,然后使用c++封装buffer数据解析,然后逻辑线程使用python实现,c++把tcp stream数据解析成完整的packet之后丢到message_queue然后逻辑线程读到数据丢给python处理。使用protobuf rpc。
目前还在开发阶段。欢迎有兴趣的来讨论。
1 0
原创粉丝点击