然后是客户端来进行调用:
import grpc import hello_pb2_grpc as pb2_grpc import hello_pb2 as pb2 channel = grpc.insecure_channel("127.0.0.1:22222") client = pb2_grpc.HelloStub(channel=channel) response = client.SayHello( pb2.HelloRequest(name="神乐 mea") ) # 返回一个 HelloResponse 对象,里面有两个属性 result1 和 result2 # result2 是一个 Response 对象,里面有 age 和 hobby 属性 print(response.result1) # 你好: 神乐 mea print(response.result2.age) # 38 print(response.result2.hobby) # money,money,money然后我们来试一下 Go 语言,它和 Python 还不太一样,在 Python 中直接通过 response.result2.age 即可,但是在 Go 中不是这样的,它有自己的一套命名规则。我们来生成 Go 源文件,注意 proto 文件不需要做任何改动。
protoc --go_out=plugins=grpc:. -I . hello.proto
package main import ( "context" "fmt" "google.golang.org/grpc" "matsuri/yoyoyo" "net" ) type Server struct { } func (s *Server) SayHello (ctx context.Context, request *yoyoyo.HelloRequest) (*yoyoyo.HelloResponse, error) { name := request.Name res := new(yoyoyo.HelloResponse) res.Result1 = fmt.Sprintf("你好呀, %s", name) // Result1 我们设置完了,但是 Result2 该怎么办?难道通过 res.Result2 = yoyoyo.HelloResponse.Response 吗? // Go 不是 Python,它没有类的概念。由于我们返回的是 HelloResponse,里面又嵌套了一个 Response,所以 Go 为它起了一个专门的名字 HelloResponse_Response // 相当于直接用下划线拼接起来了 res.Result2 = new(yoyoyo.HelloResponse_Response) res.Result2.Age = 38 res.Result2.Hobby = "money,money,money" return res, nil } func main() { // 直接进行 grpc 服务端创建、注册等逻辑没有变化 server := grpc.NewServer() yoyoyo.RegisterHelloServer(server, &Server{}) listener, err := net.Listen("tcp", ":33333") if err != nil { fmt.Println(err) return } if err = server.Serve(listener); err != nil { fmt.Println(err) return } }再来看看客户端,客户端的解析也是类似的。
package main import ( "context" "fmt" "google.golang.org/grpc" "matsuri/yoyoyo" ) func main() { conn, err := grpc.Dial("127.0.0.1:33333", grpc.WithInsecure()) if err != nil { fmt.Println(err) return } defer func() { _ = conn.Close() }() client := yoyoyo.NewHelloClient(conn) response, _ := client.SayHello(context.Background(), &yoyoyo.HelloRequest{Name: "神楽めあ"}) // 获取 Result1 fmt.Println(response.Result1) // 你好呀, 神楽めあ // 获取 Result2,返回的是一个 Response,直接调用里面的属性即可 fmt.Println(response.Result2.Age) // 38 fmt.Println(response.Result2.Hobby) // money,money,money // 当你在使用 Goland 开发 Go 程序的时候,你会发现是提示功能真的舒服。不管变量是什么类型,可以调用哪些属性都会提示给你 // 当然我们也可以 Ctrl + 左键 点击跳转,哪怕它藏得再深,你都可以准确定位到它的具体位置,这就是静态语言的优点 // 每个变量都有明确的类型,通过 IDE 的提示你就知道自己的代码有没有问题 }我们看到是完全没有问题的,当然我们之前的 Python 客户端也是可以连接的,只需要把端口改一下即可,通过 Go 客户端连接 Python 服务端也是如此。
protobuf 中的枚举类型然后我们再来看看 protobuf 中的枚举类型,当然 repeated 我们也一块说了吧。
老规矩,还是先定义 proto 文件。
syntax = "proto3"; option go_package = ".;yoyoyo"; // 对 Python 无影响 message HelloRequest { // 里面没有任何成员 } // 定义一个枚举,也可以放在 HelloResponse 里面实现 enum Gender { MALE = 0; FEMALE = 1; } // 之前的 Response,我们直接拿到外面 message Response { string hobby = 1; } message HelloResponse { int32 age = 1; // 此时的 gender 就是一个枚举类型的变量 Gender gender = 2; // 这里定义一个 hobby,它是一个数组,数组里面都是 Response,Response 里面含有一个 hobby 成员 repeated Response hobby = 3; } service Hello { rpc SayHello(HelloRequest) returns (HelloResponse) {} }