gRPC 是一个由 Google 开发的跨语言 RPC 支持库。本文系 gRPC 的入门向导型文章。
参考资料 这次直接把参考资料放在第一小节,喜欢自己阅读文档起手的朋友可以直接先看文档。
概念简介 什么是 RPC? RPC,你可以理解为是调用一个远程函数。
例说 gRPC 传输图片 准备工作 开始之前,先把演示用的仓库 clone 下来:
1 git clone https://github.com/jonblearn/simple-gRPC.git
用 VSCode 打开 ./grpc 文件夹,创建虚拟环境、并安装依赖:
1 2 python -m venv env pip install grpcio grpcio-tools numpy
解读 示例项目假设的需求情景类似于「拍立淘」:client 拍一张照片,通过 gRPC 传给 server,server 透过深度学习模型识别图上的是什么商品,把结果通过 gRPC 还给 client。
在示例项目中,下图黑笔划掉的文件是 gRPC 自动生成的、剩下的才是程序员自己写的:
每一个用到 gRPC 的项目都需要一个 proto 文件,用以定义 gRPC 里面承载的远程函数与数据类型。
打开 image_procedure.proto 文件。其中英文是原作者的注释,中文是酱瓜加的注释。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 syntax = "proto3" ; message B64Image { string b64image = 1 ; int32 width = 2 ; int32 height = 3 ; } message Prediction { int32 channel = 4 ; float mean = 5 ; } service ImageProcedure { rpc ImageMeanWH(B64Image) returns (Prediction) {} }
在终端中执行下面这行命令,就可以让 gRPC 从 proto 文件生成对应的 Python 下的数据格式定义文件 *_pb2*.py
。这两个自动生成的文件后面在 client/server 中直接调用即可,不需要做任何修改。
1 python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. image_procedure.proto
然后看 server.py:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import image_procedureimport image_procedure_pb2import image_procedure_pb2_grpcclass ImageProcedureServicer (image_procedure_pb2_grpc.ImageProcedureServicer): def ImageMeanWH (self, request, context ): response = image_procedure_pb2.Prediction() response.channel, response.mean = image_procedure.predict(request.b64image, request.width, request.height) return response server = grpc.server(futures.ThreadPoolExecutor(max_workers=12 )) image_procedure_pb2_grpc.add_ImageProcedureServicer_to_server( ImageProcedureServicer(), server) print ('Starting server. Listening on port 5005.' )server.add_insecure_port('[::]:5005' ) server.start()
最后是 client.py:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import image_procedure_pb2import image_procedure_pb2_grpcchannel = grpc.insecure_channel('127.0.0.1:5005' ) stub = image_procedure_pb2_grpc.ImageProcedureStub(channel) for _ in range (1000 ): frame = np.random.randint(0 ,255 , (416 ,416 ,3 ), dtype=np.uint8) data = frame data = base64.b64encode(data) image_req = image_procedure_pb2.B64Image(b64image = data, width = 416 , height = 416 ) response = stub.ImageMeanWH(image_req)
真正对图像进行处理的模块是 image_procedure.py:
1 2 3 4 5 6 def predict (b64img_compressed, w, h ): b64decoded = base64.b64decode(b64img_compressed) decompressed = b64decoded imgarr = np.frombuffer(decompressed, dtype=np.uint8).reshape(w, h, -1 ) return imgarr.shape[2 ], np.mean(imgarr)
总结 用 gRPC 传图片,发图的一方需要对图片进行编码、收图的一方需要先对图片进行解码,具体代码片段如下。
发送前编码 :
1 2 3 4 data = np.zeros((height, width, 3 ), np.uint8) data[:] = frame data64 = base64.b64encode(data)
然后是一些不重要的代码:
1 2 3 4 image_req = image_procedure_pb2.B64Image(b64image=data64, width=width, height=height) response = stub.SimSwap(image_req)
接收后解码 的通用代码段:
1 2 3 decompressed = base64.b64decode(response.b64image) imgarr = np.frombuffer(decompressed, dtype=np.uint8).reshape(height, width, -1 )
下面这句话意在表达 imgarr 可以直接保存成文件:
1 2 cv2.imwrite("./grpc_test/{}.jpg" .format (i), imgarr)
本节参考
性能测试 SimSwap 测试一是 SimSwap 处理一张图片的时间。
具体参数:
pic_a 是 6.jpg,pic_b 是 ds.jpg
gRPC 客户端和服务器均开在同一个机器上,即 127.0.0.1
分别处理 10 次,取平均值
结果如下:
本地处理平均耗时:1.1012424230575562 s
gRPC 处理平均耗时:1.1019452095031739 s
可见,加入 gRPC 并没有显著提高延迟。