gRPC的Golang编译及简单使用

1. 前置条件

  • Go
  • Protocol buffer编译器protoc,安装可参考Protocol buffer编译安装
  • Go 协议编译插件
    1. 下载并安装编译插件,这里确保已配置代理可以下载。
      $ export GO111MODULE=on  # Enable module mode
      $ go get google.golang.org/protobuf/cmd/protoc-gen-go \
              google.golang.org/grpc/cmd/protoc-gen-go-grpc
      #在所在项目中,执行以下命令可指定版本
      #go get github.com/golang/protobuf/protoc-gen-go@v1.0.0
    2. 添加路径到环境变量,使得protoc编译其可找到go编译插件
      $ export PATH="$PATH:$(go env GOPATH)/bin"

2. 获取示范代码

代码资源是grpc-go部分代码

```bash
#下载代码或直接下载压缩包
git clone -b v1.35.0 https://github.com/grpc/grpc-go
cd grpc-go/examples/helloworld
```

3. 运行示范代码

  • 服务端

进入目录examples/helloworld,编译并执行服务端代码

$ go run greeter_server/main.go

# server代码
package main

import (
    "context"
    "flag"
    "fmt"
    "log"
    "net"

    "google.golang.org/grpc"
    pb "google.golang.org/grpc/examples/helloworld/helloworld"
)

var (
    port = flag.Int("port", 50051, "The server port")
)

// server is used to implement helloworld.GreeterServer.
type server struct {
    pb.UnimplementedGreeterServer
}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    log.Printf("Received: %v", in.GetName())
    return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}

func main() {
    flag.Parse()
    lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port))
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    s := grpc.NewServer()
    pb.RegisterGreeterServer(s, &server{})
    log.Printf("server listening at %v", lis.Addr())
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}
  • 客户端

在另一个终端中,编译并执行客户端代码,查看客户输出:

$ go run greeter_client/main.go
Greeting: Hello world

#Client 代码
package main

import (
    "context"
    "flag"
    "log"
    "time"

    "google.golang.org/grpc"
    pb "google.golang.org/grpc/examples/helloworld/helloworld"
)

const (
    defaultName = "world"
)

var (
    addr = flag.String("addr", "localhost:50051", "the address to connect to")
    name = flag.String("name", defaultName, "Name to greet")
)

func main() {
    flag.Parse()
    // Set up a connection to the server.
    conn, err := grpc.Dial(*addr, grpc.WithInsecure())
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    c := pb.NewGreeterClient(conn)

    // Contact the server and print out its response.
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    r, err := c.SayHello(ctx, &pb.HelloRequest{Name: *name})
    if err != nil {
        log.Fatalf("could not greet: %v", err)
    }
    log.Printf("Greeting: %s", r.GetMessage())
}
  • protobuffer接口
#helloworld.proto文件

syntax = "proto3";

option go_package = "google.golang.org/grpc/examples/helloworld/helloworld";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";

package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

4. 升级gRPC服务

5. 重生成gRPC代码

在使用新服务前,需要重编译已经修改的.proto文件;
这里还是在目录examples/helloworld中,运行如下命令:

$ protoc --go_out=. --go_opt=paths=source_relative \
    --go-grpc_out=. --go-grpc_opt=paths=source_relative \
    helloworld/helloworld.proto

这样重生产了代码helloworld/helloworld.pb.go和helloworld/helloworld_grpc.pb.go,包含如下:

  • 填充、序列化、和检索的HelloRequest和HelloReply 类型;
  • 生成客户端和服务端代码.

5.1. 生成protobuf代码

  • 不需要生成xxx_grpc.pb.go文件执行如下命令
$ protoc -I "${GOBGP}"/api --go_out=plugins=grpc:${GOBGP}/api "${GOBGP}"/api/capability.proto "${GOBGP}"/api/attribute.proto

5.2. 生成包含grpc的protobuf代码

  • 如果需要生成grpc.go文件执行如下命令,${GOBGP}为某一项目路径;--go-grpc_out表示XX_rpc需要生成路径;
# service Greeter {
#   // Sends a greeting
#   rpc SayHello (HelloRequest) returns (HelloReply) {}
# }

#第一种方式(建议):
protoc -I "${GOBGP}"/api --go_out=plugins=grpc:${GOBGP}/api "${GOBGP}"/api/*.proto

#第二种方式: 需要额外二进制文件protoc-gen-go-grpc,位于项目github.com/golang/protobuf中目录protoc-gen-go
$ protoc -I "${GOBGP}"/api --go_out=${GOBGP}/api --go-grpc_out=${GOBGP}/api "${GOBGP}"/api/gobgp.proto

6. 警告缺少go_package

警告WARNING: Missing 'go_package' option in "gobgp.proto"please specify it with the full Go package path as
a future release of protoc-gen-go will require this be specified。

每个*.proto添加option go_package = "./;gobgpapi";

在syntax下面添加option信息
option go_package = "aaa;bbb";
aaa 表示生成的go文件的存放地址,会自动生成目录的。
bbb 表示生成的go文件所属的包名

7. undefined: grpc.SupportPackageIsVersion6 grpc.ClientConnInterface 问题解决

  • 问题原因
    由于protoc的 go语言插件protoc-gen-go与google.golang.org/grpc版本不兼容所致,
    因为grpc(google.golang.org/grpc)降了到了v1.36.1,高版本protoc-gen-go编译出来的your-module.pb.go不兼容低版本的grpc,所以protoc-gen-go也要相应降级。这里安装版本如下,

        github.com/golang/protobuf v1.0.0
        protoc-gen-go v1.23.0;
        protoc  建议版本 v3.7.1 ,否则编译protobuf不支持格式;
  • 方法一:下载指定版本库的具体步骤

    1. 先删除当前项目中的vendor目录: rm -rf vendor 或手动删除;
    2. 再替换go.mod中依赖库版本:
      go mod edit -require="github.com/golang/protobuf@v1.0.0"
      go mod edit -require="google.golang.org/grpc@v1.5.1"
    3. 下载指定版本v1.26.0: go get -u -x google.golang.org/grpc@v1.5.1
      go get -u -x github.com/golang/protobuf@v1.0.0
    4. 然后再go mod vendor
    5. 再运行程:go build main.go
  • 方法二
    参考该片文章undefined: proto.ProtoPackageIsVersion3


参考文章Golang gRPC Quickstart

发表评论

邮箱地址不会被公开。 必填项已用*标注