151. 【go 语言】gRPC 环境搭建(二)

proto3 语言向导链接:
Proto3 Language Guide

重要提示(2021-12-12 新增:)

本次环境搭建的相关版本如下:
protoc-3.19.1-win64
google.golang.org/grpc v1.42.0
如果版本不一致可能会导致奇奇怪怪的错误,不过作为一个合格的程序员,遇到问题应该能自己解决(版本问题一般可以通过官方文档,时间较新的技术文章)。

搭建准备(windows)

  1. 安装 protoc 编译器
    protoc 点击进入下载地址
    进入之后,找到最新的protoc-*-win64.zip下载即可。下载完之后,解压,然后将里面的 bin 目录添加到系统环境变量下即可。
  2. 安装 protoc 插件(前提是正确的搭建了 go 语言开发环境,并配备了 $GOPATH 环境变量)
go get -u google.golang.org/protobuf/cmd/protoc-gen-go
go install google.golang.org/protobuf/cmd/protoc-gen-go

在 $GOPATH 环境路径下找到刚刚执行的命令安装好的文件,我这里是下面这样的:


protoc-gen-go.exe文件路径

复制 protoc-gen-go.exe 到第 1 步里解压的目录下的 bin 目录下,我这里是下面这样的:


复制 protoc-gen-go.exe 到第一步里的 bin 目录下
  1. 继续安装 protoc 插件(2021-12-12 新增:)

2021-12-12 新增:

go get -u google.golang.org/grpc/cmd/protoc-gen-go-grpc
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc

2021-12-12 新增:


protoc-gen-go-grpc.exe 文件路径

2021-12-12 新增:
在 $GOPATH 环境路径下找到刚刚执行的命令安装好的文件,我这里现在是下面这样的:


复制 protoc-gen-go-grpc.exe 到第一步里的 bin 目录下

一、创建一个 proto 文件

文件名:helloworld.proto

syntax = "proto3";

option go_package = "proto/helloworld";

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}
  • 文件的第一行指定你正在使用proto3语法:如果你不这样做,协议缓冲区编译器将假定你正在使用proto2。这必须是文件的第一个非空、非注释行。
  • 文件的第三行 go_package 选项定义包的导入路径,该路径将包含为该文件生成的所有代码。Go包名将是导入路径的最后一个路径组件。

二、生成 proto 文件

[注意] 在项目的根目录下,执行 protoc 的相关命令,生成对应的 pd.go 文件,错误的命令如下:

protoc --go_out=plugins=grpc:. ./proto/*.proto

执行之后将会出现如下错误

E:\v4_workspace_golang\project_protoc>protoc --go_out=plugins=grpc:. ./proto/*proto
protoc-gen-go: unable to determine Go import path for "proto/helloworld.proto"

Please specify either:
        • a "go_package" option in the .proto source file, or
        • a "M" argument on the command line.

See https://developers.google.com/protocol-buffers/docs/reference/go-generated#package for more information.

--go_out: protoc-gen-go: Plugin failed with status code 1.

可能是版本的原因吧,总之别那么执行就好,参照官方文档应执行下述正确的命令:

protoc -I=. --go_out=. ./proto/*.proto

命令的定义是:

protoc -I=$SRC_DIR --go_out=$DST_DIR $SRC_DIR/*.proto

SRC_DIR(应用程序源代码所在的目录——如果不提供值,则使用当前目录),
DST_DIR(生成的代码要去的目录;通常与$SRC_DIR相同),以及.proto的路径。

执行完命令之后,在命令里指定的文件夹路径下将会生成对应的 helloworld.pb.go 文件


命令生成的文件

2021-12-12 新增
在我使用的版本里需要再次执行下面的命令:

protoc -I=. --go-grpc_out=. ./proto/*.proto

这里做个简单的说明:
我搭建环境使用的是最新的版本,和以前的版本相比,这个版本的 Service 需要单独使用 protoc-gen-go-grpc 插件,额外执行一次命令才能执行。
执行完命令之后,在命令里指定的文件夹路径下将会生成对应的 helloworld_grpc.pb.go 文件

三、简单看下 .pb.go 文件

type HelloRequest struct {
    state         protoimpl.MessageState
    sizeCache     protoimpl.SizeCache
    unknownFields protoimpl.UnknownFields

    Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
}

func (x *HelloRequest) Reset() {
    *x = HelloRequest{}
    if protoimpl.UnsafeEnabled {
        mi := &file_proto_helloworld_proto_msgTypes[0]
        ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
        ms.StoreMessageInfo(mi)
    }
}

func (x *HelloRequest) String() string {
    return protoimpl.X.MessageStringOf(x)
}

func (*HelloRequest) ProtoMessage() {}

func (x *HelloRequest) ProtoReflect() protoreflect.Message {
    mi := &file_proto_helloworld_proto_msgTypes[0]
    if protoimpl.UnsafeEnabled && x != nil {
        ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
        if ms.LoadMessageInfo() == nil {
            ms.StoreMessageInfo(mi)
        }
        return ms
    }
    return mi.MessageOf(x)
}

// Deprecated: Use HelloRequest.ProtoReflect.Descriptor instead.
func (*HelloRequest) Descriptor() ([]byte, []int) {
    return file_proto_helloworld_proto_rawDescGZIP(), []int{0}
}

func (x *HelloRequest) GetName() string {
    if x != nil {
        return x.Name
    }
    return ""
}
...

在上述代码中,主要涉及 HelloRequest 类型,其包含了一组 Getters 方法,能够提供便捷的取值方式,并且处理一些空指针取值的情况,还能通过 Reset 方法来重置该参数。而该方法通过实现 ProtoMessage 方法,以表示这是一个实现了 proto.Message 的接口。

func (*HelloRequest) Descriptor() ([]byte, []int) {
    return file_proto_helloworld_proto_rawDescGZIP(), []int{0}
}
func (*HelloReply) Descriptor() ([]byte, []int) {
    return file_proto_helloworld_proto_rawDescGZIP(), []int{1}
}

每一个 Message Type 中都包含 Descriptor 方法。Descriptor 方法指对一个消息体定义的描述,而这个方法会在 file_proto_*_proto_rawDescGZIP 中寻找对应消息体的字段(Message Field) 所在的位置后再进行返回。

四、小节

本文介绍了 Protobuf 的使用方法。proto 文件需要通过 Protobuf 的编译器 protoc 来编译后才能使用,而在各个语言的具体插件实现中,protoc-gen-go 插件是针对 Go 语言的 protoc plugin,他们是相对隔离且解耦的。未来我们可以实现一个 protoc plugin,针对企业内部的定制化需求,非常的方便。


一些从书上看的命令,实际操作时还是出错了,最终解决还是靠的官方文档。书上学习、看技术博客学习都是不错的途径,不过都有一定的滞后性,做一个软件开发者,最好的资料还是官方文档,权威、靠谱。

你可能感兴趣的:(151. 【go 语言】gRPC 环境搭建(二))