所以这算是一个需要注意的点,也不能叫坑吧,总之注意一下即可,最好的办法还是直接调用的 request 的时候直接赋值。
option go_package 的作用这个选项我们在 Python 中是不需要的,但是在 Go 里面是需要的(如果没有的话也不会报错,但是会抛警告,然后生成的文件的包名就是 proto 文件的名字)。当时我们是通过这个选项指定的包名,但其实这个选项的设置是比较灵活的,我们还可以自己指定生成文件的目录。
option go_package="common/stream/protobuf/v1"
如果改成上面这种方式的话,那么会把生成的文件放在执行命令的目录下的 common/stream/protobuf/v1 中,并且包名是 v1。如果目录不存在的话,会自动创建。并且这个选项对 Python 是无任何影响的,所以一个针对于 Go 的 proto 文件,拿给 Python 也是完全可以使用的。
proto 文件中引入其它的 proto 文件比如我们要定义多个服务,这些服务都有一个相同的方法,比如:调用 Ping,返回字符串 Pong,那么我们可以把这些公共的写在一个单独的文件中,然后去导入它。
// 文件名 common.proto syntax = "proto3"; // 我们调用 Ping 是不需要传递参数的,但是 proto 文件要求我们必须定义 // 所以我们定义一个空的消息体即可,就叫 Empty message Empty {} message Pong { string result = 1; }然后我们在其它的 proto 文件(hello.proto)导入它:
syntax = "proto3"; import "common.proto"; import "google/protobuf/empty.proto"; // 导入内置的 proto 文件 option go_package = ".;yoyoyo"; // 对 Python 无影响 message HelloRequest { string name = 1; } message HelloResponse { string message = 1; } service Hello { rpc SayHello (HelloRequest) returns (HelloResponse) {} rpc Ping(google.protobuf.Empty) returns (Pong); } // 注意到 google.protobuf.Empty 是我们从内置的 proto 文件中导入的,Pong 我们定义在 common.proto 文件中 // 但是我们将它们所在的文件都 import 进来了,所以可以直接使用然后我们来生成对应的 Python 文件,注意两个 proto 文件都要生成,所以当前目录下会多出 4 个文件。
import grpc import hello_pb2_grpc as pb2_grpc import hello_pb2 as pb2 class Hello(pb2_grpc.HelloServicer): def SayHello(self, request, context): name = request.name return pb2.HelloResponse(result=f"你好: {name}") def Ping(self, request, context): return pb2.HelloResponse(result="Pong") if __name__ == \'__main__\': from concurrent.futures import ThreadPoolExecutor grpc_server = grpc.server(ThreadPoolExecutor(max_workers=4)) pb2_grpc.add_HelloServicer_to_server(Hello(), grpc_server) grpc_server.add_insecure_port("127.0.0.1:22222") grpc_server.start() grpc_server.wait_for_termination()以上是服务端,不写注释了,相信现在很容易看懂了,然后是客户端:
import grpc import hello_pb2_grpc as pb2_grpc import hello_pb2 as pb2 from google.protobuf.empty_pb2 import Empty channel = grpc.insecure_channel("127.0.0.1:22222") client = pb2_grpc.HelloStub(channel=channel) response = client.Ping( Empty() ) print(response.result) # Pong response = client.SayHello( pb2.HelloRequest(name="神乐 mea") ) print(response.result) # 你好: 神乐 mea对于 Go 语言也是同理,可以自己测试一下,两个 Go 文件要放在一个包里面。
以上就是 proto 文件的导入,但是说实话如果不是复用的特别多的话,还是不要用导入的方式,因为每一个 proto 文件都要生成对应的两个 py 文件。
message 的嵌套我们说 message 后面的就是参数的载体,然后花括号里面的就是我们想要传递的参数。但是 message 也是可以嵌套的,下面来举个栗子。
syntax = "proto3"; option go_package = ".;yoyoyo"; // 对 Python 无影响 message HelloRequest { string name = 1; } message HelloResponse { string result1 = 1; // 这个声明我们放在外面也是可以的 message Response { int32 age = 1; string hobby = 2; } // 然后声明 Response result2 = 2; } service Hello { rpc SayHello(HelloRequest) returns (HelloResponse) {} }下面编写代码,服务端基本没有变化,只是将 Ping 这个方法给去掉了。
import grpc import hello_pb2_grpc as pb2_grpc import hello_pb2 as pb2 class Hello(pb2_grpc.HelloServicer): def SayHello(self, request, context): name = request.name # 里面的 result1 的设置很简单,但 result2 要怎么设置呢? # 我们看到 result2 这个返回的载体是谁呢?是 Response,而它在 HelloResponse 下面 # 所以直接通过 HelloResponse.Response 调用即可 return pb2.HelloResponse(result1=f"你好: {name}", result2=pb2.HelloResponse.Response(age=38, hobby="money,money,money")) if __name__ == \'__main__\': from concurrent.futures import ThreadPoolExecutor grpc_server = grpc.server(ThreadPoolExecutor(max_workers=4)) pb2_grpc.add_HelloServicer_to_server(Hello(), grpc_server) grpc_server.add_insecure_port("127.0.0.1:22222") grpc_server.start() grpc_server.wait_for_termination()