使用python通过protobuf实现rpc

欢迎转载,转载请注明原文地址: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.index
	data = request.SerializeToString()
	total_len = len(data) + 6
	self.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_service
	s_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:",e
		self.logger.log_last_except()
	return True


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

# 被调用方的Service要自己实现具体的rpc处理逻辑
class MyEchoService(IEchoService):
	
	def echo(self, controller, request, done):
		
		rpc_channel = controller.rpc_channel
		msg = request.msg
		
		response = ResponseMessage()
		response.msg = "echo:"+msg
		
		print "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。
目前还在开发阶段。欢迎有兴趣的来讨论。

猜你喜欢

转载自blog.csdn.net/majianfei1023/article/details/71628784
今日推荐