以上我们就介绍了 protobuf 中的 timestamp 类型,说实话用的还是不多,还是指定 int64、然后手动生成时间戳更方便一些。
Python 编写 gRPC 的另一种姿势我们之前是根据 proto 文件生成两个 py 文件,但其实可以不用生成,我们举个栗子:
import grpc import sys from pathlib import Path sys.path.append(str(Path(sys.prefix) / "Lib/site-packages/grpc_tools/_proto")) # 这里的 grpc.protos_and_services("hello.proto") 会返回两个模块对象 # 正好等价于生成的两个 py 文件:_pb2、_pb2_grpc。 # 但是注意:在使用之前要先将之前手动生成的两个 py 文件删掉,如果它们和你当前的客户端或服务端在同一个目录的话 pb2, pb2_grpc = grpc.protos_and_services("hello.proto") """ import hello_pb2_grpc as pb2_grpc import hello_pb2 as pb2 """ # grpc.protos_and_services("hello.proto") 返回的 pb2, pb2_grpc 和 hello.proto 生成的两个模块是等价的 channel = grpc.insecure_channel("127.0.0.1:33333") client = pb2_grpc.HelloStub(channel=channel) response = client.SayHello( pb2.HelloRequest() ) print(response.info) # {\'name\': \'夏色祭\', \'age\': \'16\', \'hobby\': \'斯哈斯哈\'} print(response.t.seconds) # 1532016000我们看到这样就方便多了,根本不需要生成两个文件了,不仅是客户端、服务端也是如此。但是我们注意到,我们上面将 site-packages 目录下的 grpc_tools/_proto 加入到了 sys.path 中,这是因为内置的 proto 文件在这里面。如果是这种导入方式的话,那么必须要有这一步,否则是找不到这个文件的。
之前我们还觉得 Python 比较麻烦,别的语言只有一个文件,唯独它有两个,但是现在看来 Python 反而要更简单一些,因为一个文件都没有了。
grpc 搭配 asyncio 使用我们在使用 Python 编写 gRPC 服务端的时候,使用的是线程池;而 Go 编写服务端,虽然我们没有手动启动 goroutine,但是内部在处理连接的时候是使用 goroutine 来处理的,因此 Go 的并发量会非常高。但有时我们也会使用 Python 编写 gRPC 服务,那么能不能想个办法提高一下它的并发量呢?显然如果想做到这一点,那么 Python 也使用协程就可以了,那么下面我们就来介绍 gRPC 如何搭配 asyncio 使用。
grpc 这个库官方提供了一个 aio 子模块,但是支持的还不是特别完美,并且文档也不全,所以这里推荐另一个库叫 grpclib。安装的话,直接 pip 安装即可。
然后编写我们的 proto 文件,和之前类似,但是简化了许多。
syntax = "proto3"; option go_package = ".;yoyoyo"; // 对 Python 无影响 message HelloRequest { } message HelloResponse { map<string, string> info = 1; } service Hello { rpc SayHello(HelloRequest) returns (HelloResponse) {} }然后我们来生成对应的 py 文件,注意:我们现在使用的是 grpclib,所以命令变了,需要将 grpc_python_out 换成 grpclib_python_out。
python -m grpc_tools.protoc --python_out=. --grpclib_python_out=. -I. hello.proto
对于 grpclib 的话,还是需要先生成 py 文件的,下面来看看如何使用 grpclib 来编写服务端:
import asyncio from grpclib.server import Server # 生成的文件叫 hello_pb2 和 hello_grpc(不是 hello_pb2_grpc) import hello_grpc as pb2_grpc import hello_pb2 as pb2 # 之前继承的类叫做 HelloServicer,现在变成了 HelloBase class Hello(pb2_grpc.HelloBase): # 它是一个抽象基类,我们必须实现里面定义的方法 async def SayHello(self, stream): # 这个返回的 request 就相当于之前说的 grpc 中的参数的载体 # 这里我们没有参数,所以没啥卵用,只是获取一下 request = await stream.recv_message() # 然后返回内容,不使用 return,使用 stream.send_message,里面接收 HelloResponse 对象 await stream.send_message(pb2.HelloResponse(info={"name": "凑阿库娅", "age": "5"})) async def main(host="127.0.0.1", port=22222): server = Server([Hello()]) await server.start(host, port) await server.wait_closed() if __name__ == \'__main__\': asyncio.run(main())然后是客户端,我们先来看看 Go 的客户端可不可以调用,用之前的 proto 文件生成一下 Go 对应的源文件,然后编写客户端。
package main import ( "context" "fmt" "google.golang.org/grpc" "matsuri/yoyoyo" ) func main() { conn, err := grpc.Dial("127.0.0.1:22222", grpc.WithInsecure()) if err != nil { fmt.Println(err) return } defer func() { _ = conn.Close() }() client := yoyoyo.NewHelloClient(conn) response, _ := client.SayHello(context.Background(), &yoyoyo.HelloRequest{}) fmt.Println(response.Info) // map[age:5 name:凑阿库娅] fmt.Println(response.Info["name"]) // 凑阿库娅 }