protobuf是在项目中经常会用到的一个库,它提供了方便的工具和接口,可以对结构化数据进行序列化和反序列化,便于网络传输。
其实,如果将一个函数调用用结构化数据表示出来,利用protobuf序列化后通过网络传递到远端,在远端进行反序列化解析,就自然地实现了rpc(Remote Procedure Call)的功能。
protobuf中保留了关键字rpc,并且提供了一个RpcChannel的类,供开发者自己实现rpc框架。实现这个rpc框架,其实主要是实现RpcChannel::CallMethod这个接口。我们自己的项目中,就使用了一套自己实现的基于ansyncore的RpcChannel,而某度最近也开源了其基于protobuf的rpc框架,网络部分是使用的Boost::Asio,有兴趣的读者可以自行前往其github wiki页面学习。
那grpc呢,则是google自己基于protobuf(也是google自己开发的库)实现的一套rpc框架。这里使用一张官网的图,表示下其基础架构:
一个简单的例子
可能光说不练有点抽象,那么下面用一个例子说明下grpc的基本用法。
说到网络相关的例子,简单而又实用的当属EchoServer了。
定义一个EchoServer只需一个接口Echo,它接受一条字符串消息,并原样返回一条字符串消息。因此,echo_server.proto文件定义如下:
syntax = "proto3";
package echo_server;
service EchoServer
{
rpc Echo (EchoRequest) returns (EchoReply) {}
}
message EchoRequest
{
string msg = 1;
}
message EchoReply
{
string msg = 1;
}
首先,grpc使用protobuf3.x版本,因此需要在开头声明syntax=”proto3”,剩下的部分和c语言的语法很类似,基本上有了例子之后,照猫画虎很容易就可以写出来自己需要的proto文件。
有了proto文件之后,需要使用protoc将其编译生成对应的py文件。这里grpc提供了一个grpc_tools的库,可以将这一过程程序化:
from grpc.tools import protoc
protoc.main(
(
'',
'-I.',
'--python_out=.',
'--grpc_python_out=.',
'./echo_server.proto',
)
)
生成的echo_server_pb2.py文件中,就定义了我们实现这个EchoServer所需的Servicer类和Stub类。
Server
先看server的实现。
首先,需要定义一个类,继承自xxxServicer(这里是EchoServerServicer),并重写其Echo方法。
class EchoServer(echo_server_pb2.EchoServerServicer):
def Echo(self, request, context):
return echo_server_pb2.EchoReply(msg='echo:%s' % request.msg)
可以看到,其中的request和return值,都是按照我们在proto文件中的定义生成的python类型,非常直观。
如何将这个EchoServer类和rpc服务绑定在一起,也是有套路的:
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
echo_server_pb2.add_EchoServerServicer_to_server(EchoServer(), server)
server.add_insecure_port('[::]:50015')
server.start()
try:
while True:
time.sleep(60*60)
except KeyboardInterrupt:
server.stop(0)
由于server.start()是一个非阻塞式调用,因此需要在后面用一个死循环来防止程序终止/GC导致rpc服务不可用。
Client
Client的实现就更简单了,只需通过ip和port创建一个channel,然后利用这个channel创建一个本地的Stub,然后就可以直接Stub.Echo调用远端的Echo方法了,Stub会帮你处理好一切其他事务(构造调用的结构化数据,序列化,网络传输等等)。
from __future__ import print_function
import grpc
import echo_server_pb2
import sys
if sys.version_info.major == 3:
raw_input = input
else:
raw_input = raw_input
def run():
channel = grpc.insecure_channel('localhost:50015')
stub = echo_server_pb2.EchoServerStub(channel)
while True:
msg = raw_input('you say:')
reply = stub.Echo(echo_server_pb2.EchoRequest(msg=msg))
print(reply.msg)
pass
if __name__ == "__main__":
run()
这段代码中对print和raw_input这两个py2和py3不兼容的调用打了Monkey Patch,从而使得这段程序可以同时运行在py2和py3的环境中~
总结
至此,grpc的最基础的应用就说的差不多了。除了上面的阻塞式调用,grpc还提供了非阻塞式调用(future)。另外,对于传递的参数和返回值,grpc还支持流式参数(stream、yield)。具体的相关例子,有兴趣的读者可以前往grpc的官网查询。
接下来,我将会使用grpc做一个message hub的应用,功能上应该可以替代目前项目中使用的hub(性能上就不指望替代了,毕竟当前hub使用c++实现的),当作一个练手的实际项目。敬请期待^_^
完整代码详见grpc
转载请注明出处: blog.guoyb.com/2016/10/15/…
欢迎使用微信扫描下方二维码,关注我的微信公众号TechTalking,技术·生活·思考: