python之gRPC初试

前言

本文章作为初学gRPC的一个记录,主要参考文档:Quick start gRPC

准备

  • python 3.5 +

  • pip 9.0.1 +

安装

  • 安装gRPC
$pip install grpcio
  • 安装gRPC tools

该工具包括了协议缓冲区编译器protoc和用于从.proto文件定义生成特定的服务器和客户端代码的插件。

$pip install grpcio-tools

示例

可以参考文档中的方式,通过github获取示例代码

# Clone the repository to get the example code:
$ git clone -b v1.50.0 --depth 1 --shallow-submodules https://github.com/grpc/grpc
# Navigate to the "hello, world" Python example:
$ cd grpc/examples/python/helloworld

编写proto文件

demo.proto

//需要使用的proto版本
syntax = "proto3";

//对服务的定义
service MsgService {
 //服务提供的方法定义
 rpc GetMsg (MsgRequest) returns (MsgResponse){}
}

//服务请求消息的定义
message MsgRequest {
  string name = 1;
}

//服务响应消息的定义
message MsgResponse {
  string msg = 1;
}

syntax指定了需要使用的proto协议的版本,具体的去吧大家可以自行搜索,我作为一个初学者目前还没有涉及到太深,后续会根据学习的深入对相关内容进行补充。(文件第一行非空白非注释行必须写syntax="proto3", 使用proto2则更换为proto2)

service MsgService 是对服务的定义,类似于一个class,或者interface。语法为service ServiceName {}。

在大括号里面的需要定义提供的服务方法,即rpc服务。rpc GetMsg (MsgRequest) returns (MsgResponse) {}表明定义了一个GetMsg的方法,客户端传递一个MsgRequest类型的请求体,该方法最后会返回一个MsgResponse类型的响应体。姑且可以看作是http协议中的request和response所荷载的信息。

message MsgRequest{}和message MsgResponse{}是对提供的服务方法中消息的定义。上述文件中的string name = 1;表明该消息体中包含一个string类型的,名称为name的变量, 等于1表示在该消息体中的索引位置(如果说错了多谢评论指正)。同理在响应体定义中,定义了一个string类型的名称为msg的变量。

通过proto文件生成_pb2.py和_pb2_grpc.py文件

_pb2.py文件会对每一个message进行对应的信息存储,在处理service和client文件的时候会使用

_pb2_grpc.py用来存储每个服务的server与client以及注册server的工具

执行命令,生成相应的文件

$python -m grpc_tools.protoc -I . --python_out=. --grpc_python_out=. demo.proto

命令表示将pb2 和 pb2_grpc文件都生成到当前目录,指定proto文件为执行命令目录下的demo.proto文件

这里不对两个文件内容进行张贴了,大家可以自己按照上面的步骤生成,然后去参考生成的文件内容即可。

编写server文件

demo_server.py

import grpc
import demo_pb2
import demo_pb2_grpc

from concurrent import futures
import time

_ONE_DAY_IN_SECONDS = 60 * 60 * 24


class MsgServicer(demo_pb2_grpc.MsgServiceServicer):

    def GetMsg(self, request, context):
        print("Received name: %s" % request.name)
        return demo_pb2.MsgResponse(msg='Hello, %s!' % request.name)


def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    demo_pb2_grpc.add_MsgServiceServicer_to_server(MsgServicer(), server)
    server.add_insecure_port('[::]:5001')
    server.start()
    try:
        while True:
            time.sleep(_ONE_DAY_IN_SECONDS)
    except KeyboardInterrupt:
        server.stop(0)


if __name__ == '__main__':
    serve()
    

首先定义服务和服务方法。

MsgServicer继承自通过工具生成的MsgServiceServicer。

需要实现一个定义的GetMsg方法。方法需要接收一个request(如果是流传递,则是request_iterator,本文先只针对单项情况下进行说明,后续补充关于流传递的内容)对象和一个context上下文对象

由于我们定义了request(即MsgResquest)包含一个name,所以这里可以通过request.name获取到客户端传递过来的值。

GetMsg定义返回一个MsgResponse的对象类型的结果,所以可以通过pb2获取到该类型,并对其赋值

通过对MsgServicer的实现,我们完成了服务端服务的定义

接下来是对服务的注册

首先通过grpc.server()来获取一个server的对象,grpc服务是支持多线程的,但是需要通过工具来配合使用,于是有了代码中的futures,并且定义了10个线程。

通过pb2_grpc文件中提供的注册方法,将该服务类进行注册

设置一个监听端口,本文是一个本地5001的端口

由于服务开启后不会一直保留(python的锅),所以通过一个while True进行阻塞

编写client文件

demo_client.py

import grpc

import demo_pb2
import demo_pb2_grpc


def run():
    with grpc.insecure_channel('localhost:50051') as channel:
        stub = demo_pb2_grpc.MsgServiceStub(channel)
        response = stub.GetMsg(demo_pb2.MsgRequest(name='world'))
    print("Client received: " + response.msg)


if __name__ == '__main__':
    run()
 

(只需要关注run方法即可)

通过grpc申请一个channel,通过pb2_grpc中提供的客户端,进行客户端渠道对接stub = demo_pb2_grpc.MsgServiceStub(channel)。请求rpc服务并获取响应结果,response=stub.GetMsg(demo_pb2.MsgRequest(name="world"))

运行

  • 先开启服务端
python demo_server.py
  • 再开启客户端
python demo_client.py

可以看到服务端处于一直运行状态,客户端启动后获取到响应结果,服务端也打印相关内容(如果存在打印处理)

  • 服务端显示内容

服务端

  • 客户端显示内容

客户端

结语

对于一个编程人来说,一个新的东西并不可怕,只要愿意动手,愿意尝试,也能对能力进行提升。保持学习,保持进步。

posted @ 2022-12-02 16:58  一豆夫  阅读(184)  评论(0编辑  收藏  举报