proto3 語言向?qū)ф溄樱?br> Proto3 Language Guide
重要提示(2021-12-12 新增:)
本次環(huán)境搭建的相關(guān)版本如下:
protoc-3.19.1-win64
google.golang.org/grpc v1.42.0
如果版本不一致可能會(huì)導(dǎo)致奇奇怪怪的錯(cuò)誤,不過作為一個(gè)合格的程序員,遇到問題應(yīng)該能自己解決(版本問題一般可以通過官方文檔,時(shí)間較新的技術(shù)文章)。
搭建準(zhǔn)備(windows)
- 安裝 protoc 編譯器
protoc 點(diǎn)擊進(jìn)入下載地址
進(jìn)入之后,找到最新的protoc-*-win64.zip下載即可。下載完之后,解壓,然后將里面的 bin 目錄添加到系統(tǒng)環(huán)境變量下即可。 - 安裝 protoc 插件(前提是正確的搭建了 go 語言開發(fā)環(huán)境,并配備了 $GOPATH 環(huán)境變量)
go get -u google.golang.org/protobuf/cmd/protoc-gen-go
go install google.golang.org/protobuf/cmd/protoc-gen-go
在 $GOPATH 環(huán)境路徑下找到剛剛執(zhí)行的命令安裝好的文件,我這里是下面這樣的:

復(fù)制 protoc-gen-go.exe 到第 1 步里解壓的目錄下的 bin 目錄下,我這里是下面這樣的:

- 繼續(xù)安裝 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 新增:

2021-12-12 新增:
在 $GOPATH 環(huán)境路徑下找到剛剛執(zhí)行的命令安裝好的文件,我這里現(xiàn)在是下面這樣的:

一、創(chuàng)建一個(gè) 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語法:如果你不這樣做,協(xié)議緩沖區(qū)編譯器將假定你正在使用proto2。這必須是文件的第一個(gè)非空、非注釋行。
- 文件的第三行 go_package 選項(xiàng)定義包的導(dǎo)入路徑,該路徑將包含為該文件生成的所有代碼。Go包名將是導(dǎo)入路徑的最后一個(gè)路徑組件。
二、生成 proto 文件
[注意] 在項(xiàng)目的根目錄下,執(zhí)行 protoc 的相關(guān)命令,生成對(duì)應(yīng)的 pd.go 文件,錯(cuò)誤的命令如下:
protoc --go_out=plugins=grpc:. ./proto/*.proto
執(zhí)行之后將會(huì)出現(xiàn)如下錯(cuò)誤
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.
可能是版本的原因吧,總之別那么執(zhí)行就好,參照官方文檔應(yīng)執(zhí)行下述正確的命令:
protoc -I=. --go_out=. ./proto/*.proto
命令的定義是:
protoc -I=$SRC_DIR --go_out=$DST_DIR $SRC_DIR/*.proto
SRC_DIR(應(yīng)用程序源代碼所在的目錄——如果不提供值,則使用當(dāng)前目錄),
DST_DIR(生成的代碼要去的目錄;通常與$SRC_DIR相同),以及.proto的路徑。
執(zhí)行完命令之后,在命令里指定的文件夾路徑下將會(huì)生成對(duì)應(yīng)的 helloworld.pb.go 文件

2021-12-12 新增
在我使用的版本里需要再次執(zhí)行下面的命令:
protoc -I=. --go-grpc_out=. ./proto/*.proto
這里做個(gè)簡單的說明:
我搭建環(huán)境使用的是最新的版本,和以前的版本相比,這個(gè)版本的 Service 需要單獨(dú)使用 protoc-gen-go-grpc 插件,額外執(zhí)行一次命令才能執(zhí)行。
執(zhí)行完命令之后,在命令里指定的文件夾路徑下將會(huì)生成對(duì)應(yīng)的 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 方法來重置該參數(shù)。而該方法通過實(shí)現(xiàn) ProtoMessage 方法,以表示這是一個(gè)實(shí)現(xiàn)了 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}
}
每一個(gè) Message Type 中都包含 Descriptor 方法。Descriptor 方法指對(duì)一個(gè)消息體定義的描述,而這個(gè)方法會(huì)在 file_proto_*_proto_rawDescGZIP 中尋找對(duì)應(yīng)消息體的字段(Message Field) 所在的位置后再進(jìn)行返回。
四、小節(jié)
本文介紹了 Protobuf 的使用方法。proto 文件需要通過 Protobuf 的編譯器 protoc 來編譯后才能使用,而在各個(gè)語言的具體插件實(shí)現(xiàn)中,protoc-gen-go 插件是針對(duì) Go 語言的 protoc plugin,他們是相對(duì)隔離且解耦的。未來我們可以實(shí)現(xiàn)一個(gè) protoc plugin,針對(duì)企業(yè)內(nèi)部的定制化需求,非常的方便。
一些從書上看的命令,實(shí)際操作時(shí)還是出錯(cuò)了,最終解決還是靠的官方文檔。書上學(xué)習(xí)、看技術(shù)博客學(xué)習(xí)都是不錯(cuò)的途徑,不過都有一定的滯后性,做一個(gè)軟件開發(fā)者,最好的資料還是官方文檔,權(quán)威、靠譜。