<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          protobuf Go語言教程

          共 16278字,需瀏覽 33分鐘

           ·

          2022-07-26 05:48

          protocol buffers

          protobuf介紹

          Protobuf全稱Protocol Buffer,是 Google 公司于2008年開源的一種語言無關(guān)、平臺無關(guān)、可擴展的用于序列化結(jié)構(gòu)化數(shù)據(jù)——類似于XML,但比XML更小、更快、更簡單,它可用于(數(shù)據(jù))通信協(xié)議、數(shù)據(jù)存儲等。你只需要定義一次你想要的數(shù)據(jù)結(jié)構(gòu),然后你就可以使用特殊生成的源代碼來輕松地從各種數(shù)據(jù)流和各種語言中寫入和讀取你的結(jié)構(gòu)化數(shù)據(jù)。目前 Protobuf 被廣泛用作微服務中的通信協(xié)議。

          protobuf語法

          點擊查看 protobuf v3語法中文指南

          protobuf 編譯器指南

          安裝 protobuf

          從下面的鏈接:https://github.com/google/protobuf/releases下載適合你平臺的預編譯好的二進制文件(protoc-<version>-<platform>.zip)。

          • 適用Windows 64位protoc-3.20.1-win64.zip
          • 適用于Mac Intel 64位protoc-3.20.1-osx-x86_64.zip
          • 適用于Mac ARM 64位protoc-3.20.1-osx-aarch_64.zip
          • 適用于Linux 64位protoc-3.20.1-linux-x86_64.zip

          例如,我使用 Intel 芯片的 Mac 系統(tǒng)則下載 protoc-3.20.1-osx-x86_64.zip 文件,解壓之后得到如下內(nèi)容。

          protoc

          其中:

          • bin 目錄下的 protoc 是可執(zhí)行文件。
          • include 目錄下的是 google 定義的.proto文件,我們import "google/protobuf/timestamp.proto"就是從此處導入。

          我們需要將下載得到的可執(zhí)行文件protoc所在的 bin 目錄加到我們電腦的環(huán)境變量中。

          安裝生成Go代碼的插件

          go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

          生成Go代碼

          編譯器調(diào)用

          protocol buffer編譯器需要一個插件來根據(jù)提供的proto文件生成 Go 代碼,Go1.16+請使用下面的命令安裝插件。

          go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

          這個命令將在 $GOBIN 中安裝一個 protocol-gen-go 的二進制文件。我們需要確保 $GOBIN 在你的環(huán)境變量中,protocol buffer編譯器才能找到它(可以通過go env命令查看$GOPATH)。

          當使用go_out 標志調(diào)用 protoc 時,protocol buffer編譯器將生成 Go 代碼。protocol buffer編譯器會將生成的Go代碼輸出到命令行參數(shù)go_out指定的位置。go_out標志的參數(shù)是您希望編譯器編寫 Go 輸出的目錄。編譯器為每個.proto 文件輸入創(chuàng)建一個源文件。輸出文件的名稱是通過將.proto 擴展名替換為.pb.go 而創(chuàng)建的。

          生成的.pb.go文件放置的目錄取決于編譯器標志。有幾種輸出模式:

          • paths=import:輸出文件放在以 Go 包的導入路徑命名的目錄中。例如,protos/buzz.proto文件中帶有example.com/project/protos/fizz的導入路徑,則輸出的生成文件會保存在example.com/project/protos/fizz/buzz.pb.go。如果未指定路徑標志,這就是默認輸出模式。
          • module=$PREFIX:輸出文件放在以 Go 包的導入路徑命名的目錄中,但是從輸出文件名中刪除了指定的目錄前綴。例如,輸入文件 pros/buzz.proto,其導入路徑為 example.com/project/protos/fizz 并指定example.com/projectmodule前綴,結(jié)果會產(chǎn)生一個名為 pros/fizz/buzz.pb.go 的輸出文件。在module路徑之外生成任何 Go 包都會導致錯誤。此模式對于將生成的文件直接輸出到 Go 模塊非常有用。
          • paths=source_relative:輸出文件與輸入文件放在相同的相對目錄中。例如,一個protos/buzz.proto輸入文件會產(chǎn)生一個位于protos/buzz.pb.go的輸出文件。

          在調(diào)用protoc時,通過傳遞 go_opt 標志來提供特定于 protocol-gen-go 的標志位參數(shù)。可以傳遞多個go_opt標志位參數(shù)。例如,當執(zhí)行下面的命令時:

          protoc --proto_path=src --go_out=out --go_opt=paths=source_relative foo.proto bar/baz.proto

          編譯器將從 src 目錄中讀取輸入文件 foo.protobar/baz.proto,并將輸出文件 foo.pb.gobar/baz.pb.go 寫入 out 目錄。如果需要,編譯器會自動創(chuàng)建嵌套的輸出子目錄,但不會創(chuàng)建輸出目錄本身。

          package

          為了生成 Go 代碼,必須為每個 .proto 文件(包括那些被生成的 .proto 文件傳遞依賴的文件)提供 Go 包的導入路徑。有兩種方法可以指定 Go 導入路徑:

          • 通過在 .proto 文件中聲明它,或者
          • 通過在調(diào)用 protoc 時在命令行上聲明它。

          我們建議在 .proto 文件中聲明它,以便 .proto 文件的 Go 包可以與 .proto 文件本身集中標識,并簡化調(diào)用 protoc 時傳遞的標志集。如果給定 .proto 文件的 Go 導入路徑由 .proto 文件本身和命令行提供,則后者優(yōu)先于前者。

          Go 導入路徑是在 .proto 文件中指定的,通過聲明帶有 Go 包的完整導入路徑的 go_package 選項來創(chuàng)建 proto 文件。用法示例:

          option go_package = "example.com/project/protos/fizz";

          調(diào)用編譯器時,可以在命令行上指定 Go 導入路徑,方法是傳遞一個或多個 M${PROTO_FILE}=${GO_IMPORT_PATH} 標志位。用法示例:

          protoc --proto_path=src \
            --go_opt=Mprotos/buzz.proto=example.com/project/protos/fizz \
            --go_opt=Mprotos/bar.proto=example.com/project/protos/foo \
            protos/buzz.proto protos/bar.proto

          由于所有 .proto 文件到其 Go 導入路徑的映射可能非常大,這種指定 Go 導入路徑的模式通常由控制整個依賴樹的某些構(gòu)建工具(例如 Bazel)執(zhí)行。如果給定的 .proto 文件有重復條目,則指定的最后一個條目優(yōu)先。

          對于 go_package 選項和 M 標志位,它們的值可以包含一個顯式的包名稱,該名稱與導入路徑之間用分號分隔。例如:“example.com/protos/foo;package_name”。不鼓勵這種用法,因為默認情況下包名稱將以合理的方式從導入路徑派生。

          導入路徑用于確定一個 .proto 文件導入另一個 .proto 文件時必須生成哪些導入語句。例如,如果 a.proto 導入 b.proto,則生成的 a.pb.go 文件需要導入包含生成的 b.pb.go 文件的 Go 包(除非兩個文件在同一個包中)。導入路徑也用于構(gòu)造輸出文件名。有關(guān)詳細信息,請參閱上面的“編譯器調(diào)用”部分。

          Go 導入路徑和 .proto 文件中的package說明符之間沒有關(guān)聯(lián)。后者僅與 protobuf 命名空間相關(guān),而前者僅與 Go 命名空間相關(guān)。此外,Go 導入路徑和 .proto 導入路徑之間沒有關(guān)聯(lián)。

          Go語言使用protoc示例

          我們新建一個名為demo的項目,并且將項目中定義的.proto文件都保存在proto目錄下。

          本文后續(xù)的操作命令默認都在demo目錄下執(zhí)行。

          普通編譯

          定義proto

          // proto/book/book.proto

          syntax = "proto3";

          package book;

          // 聲明生成Go代碼的導入路徑(import path)
          option go_package = "github.com/Q1mi/demo/proto/book";

          message price {
          int64 marketPrice = 1;
          int64 salePrice = 2;
          }

          我們在這個文件中使用option go_package = "github.com/Q1mi/demo/proto/book"語句聲明了生成的Go代碼的導入路徑。

          項目當前的目錄結(jié)構(gòu)如下:

          demo
          └── proto
              └── book
                  └── price.proto

          生成代碼

          假設(shè)我們想把最終生成的Go代碼還保存在proto文件夾中,那么就可以執(zhí)行下面的命令。

          protoc \
          --proto_path=proto \
          --go_out=proto \
          --go_opt=paths=source_relative \
          book/price.proto

          其中:

          • --proto_path=proto 表示從proto目錄下讀取proto文件。
          • --go_out=proto 表示生成的Go代碼保存的路徑。
          • --go_opt=paths=source_relative 表示輸出文件與輸入文件放在相同的相對目錄中。
          • account/account.proto 表示在proto目錄下的account/account.proto文件。

          此外,--proto_path有一個別名-I,上述編譯命令也可以這樣寫。

          protoc \
          -I=proto \
          --go_out=proto \
          --go_opt=paths=source_relative \
          book/price.proto

          執(zhí)行上述命令將會在proto目錄下生成account/account.pb.go文件。

          demo
          └── proto
              └── book
                  ├── price.pb.go
                  └── price.proto

          此處命令可以簡寫為:

          protoc \
          --go_out=. \
          --go_opt=paths=source_relative \
          proto/book/price.proto

          上面的命令都是將代碼生成到demo/proto目錄,如果想要將生成的Go代碼保存在其他文件夾中(例如pb文件夾),那么我們需要先在demo目錄下創(chuàng)建一個pb文件夾。然后在命令行通過--go_out=pb指定生成的Go代碼保存的路徑。完整命令如下:

          protoc \
          --proto_path=proto \
          --go_out=pb \
          --go_opt=paths=source_relative \
          book/price.proto

          執(zhí)行上面的命令便會在demo/pb文件夾下生成Go代碼。

          demo
          ├── pb
          │   └── book
          │       └── price.pb.go
          └── proto
              └── book
                  ├── price.pb.go
                  └── price.proto

          import同目錄下protobuf文件

          隨著業(yè)務的復雜度上升,我們可能會定義多個.proto源文件,然后根據(jù)需要引入其他的protobuf文件。

          在這個示例中,我們在demo/proto/book目錄下新建一個book.proto文件,它通過import "book/price.proto";語句引用了同目錄下的price.proto文件。

          // demo/proto/book/book.proto

          syntax = "proto3";

          // 聲明protobuf中的包名
          package book;

          // 聲明生成的Go代碼的導入路徑
          option go_package = "github.com/Q1mi/demo/proto/book";

          // 引入同目錄下的protobuf文件(注意起始位置為proto_path的下層)
          import "book/price.proto";

          message book {
          string title = 1;
          price price = 2;
          }

          編譯命令如下:

          protoc \
          --proto_path=proto \
          --go_out=proto \
          --go_opt=paths=source_relative \
          book/book.proto book/price.proto

          這里有幾點需要注意:

          1. 因為我們通過編譯命令指定--proto_path=proto,所以import導入語句需要從demo/proto文件夾的下層目錄book這一層開始寫。

          2. 因為導入的price.protobook.proto同屬于一個package book;,所以可以直接使用price作為類型,無需添加 package 前綴(即無需寫成book.price)。

          上述編譯命令最終會生成demo/proto/book/book.pb.go文件。

          demo
          └── proto
              └── book
                  ├── book.pb.go
                  ├── book.proto
                  ├── price.pb.go
                  └── price.proto

          import其他目錄下文件

          我們在demo/proto目錄下新建了一個author文件夾,用來存放與 author 相關(guān)的 protobuf 文件。例如我們定義一個表示作者信息的author.proto文件,其內(nèi)容如下:

          // demo/proto/author/author.proto

          syntax = "proto3";

          // 聲明protobuf中的包名
          package author;

          // 聲明生成的Go代碼的導入路徑
          option go_package = "github.com/Q1mi/demo/proto/author";

          message Info {
          string name = 1;
          }

          此時的目錄結(jié)構(gòu):

          demo
          └── proto
              ├── author
              │   └── author.proto
              └── book
                  ├── book.pb.go
                  ├── book.proto
                  ├── price.pb.go
                  └── price.proto

          假設(shè)我們的 book 需要增加一個作者信息的字段——authorInfo,這時我們需要在demo/proto/book/book.proto中導入其他目錄下的 author.proto 文件。具體改動如下。

          // proto/proto/book/book.proto

          syntax = "proto3";

          // 聲明protobuf中的包名
          package book;

          // 聲明生成的Go代碼的導入路徑
          option go_package = "github.com/Q1mi/demo/proto/book";

          // 引入同目錄下的protobuf文件(注意起始位置為proto_path的下層)
          import "book/price.proto";
          // 引入其他目錄下的protobuf文件
          import "author/author.proto";

          message book {
          string title = 1;
          price price = 2;
          author.Info authorInfo = 3; // 需要帶package前綴
          // google.protobuf.Timestamp date = 4;
          }

          我們通過import "author/author.proto";導入了author包的author.proto文件,所以在book包下使用Info類型時需要添加其包名前綴即author.Info

          編譯命令如下:

          protoc \
          --proto_path=proto \
          --go_out=proto \
          --go_opt=paths=source_relative \
          book/book.proto book/price.proto author/author.proto

          此時的目錄結(jié)構(gòu):

          demo
          └── proto
              ├── author
              │   ├── author.pb.go
              │   └── author.proto
              └── book
                  ├── book.pb.go
                  ├── book.proto
                  ├── price.pb.go
                  └── price.proto

          import google proto文件

          有時候我們也需要在我們定義的 protobuf 文件中使用 Google 定義的類型,例如TimestampAny等。

          例如我們要為我們的 book 添加出版日期——date字段,就可以通過 import "google/protobuf/timestamp.proto";導入并使用Timestamp類型了。

          修改后的book.proto文件內(nèi)容如下:

          // demo/proto/book/book.proto

          syntax = "proto3";

          // 聲明protobuf中的包名
          package book;

          // 聲明生成的Go代碼的導入路徑
          option go_package = "github.com/Q1mi/demo/proto/book";

          // 引入同目錄下的protobuf文件(注意起始位置為proto_path的下層)
          import "book/price.proto";
          // 引入其他目錄下的protobuf文件
          import "author/author.proto";
          // 引入google/protobuf/timestamp.proto文件
          import "google/protobuf/timestamp.proto";

          message book {
          string title = 1;
          price price = 2;
          author.Info authorInfo = 3; // 需要帶package前綴
          // Timestamp是大寫T!大寫T!大寫T!
          google.protobuf.Timestamp date = 4; // 注意包名前綴
          }

          那么這個 google/protobuf/timestamp.proto 是從哪里導入的呢?

          通常我們下載 protobuf編譯器的時候會解壓得到如下文件:

          protoc

          其中:

          • bin 目錄下的 protoc 是可執(zhí)行文件。
          • include 目錄下的是 google 定義的.proto文件,我們import "google/protobuf/timestamp.proto"就是從此處導入。

          我們需要將下載得到的可執(zhí)行文件protoc所在的 bin 目錄加到我們電腦的環(huán)境變量中。

          如果你不是通過這種方式安裝的 protobuf 那么你也可以手動將 Google 定義的protobuf文件下載到本地(git clone或者go get,protobuf文件在src下),然后通過 --proto_path指定其路徑。

          protoc \
          --proto_path=/Users/liwenzhou/workspace/go/pkg/mod/github.com/protocolbuffers/[email protected]+incompatible/src/ \
          --proto_path=proto \
          --go_out=proto \
          --go_opt=paths=source_relative \
          book/book.proto book/price.proto author/author.proto

          或者你還可以簡單粗暴的把下載好的 protobuf 文件拷貝到你項目的 proto 目錄下。

          demo
          └── proto
              ├── author
              │   ├── author.pb.go
              │   └── author.proto
              ├── book
              │   ├── book.pb.go
              │   ├── book.proto
              │   ├── price.pb.go
              │   └── price.proto
              └── google
                  └── protobuf
                      └── timestamp.proto

          然后執(zhí)行下面的編譯命令:

          protoc \
          --proto_path=proto \
          --go_out=proto \
          --go_opt=paths=source_relative \
          book/book.proto book/price.proto author/author.proto

          生成gRPC代碼

          由于通常我們都是配合 gRPC 來使用 protobuf ,所以我們也需要基于.proto文件生成Go代碼的同時生成 gRPC 代碼。

          要想生成 gRPC 代碼就需要先安裝 protoc-gen-go-grpc 插件。

          go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

          上述命令會默認將插件安裝到$GOPATH/bin,為了protoc編譯器能找到這些插件,請確保你的$GOPATH/bin在環(huán)境變量中。

          假設(shè)我們現(xiàn)在要提供一個創(chuàng)建書籍的 RPC 方法,那么我在book.proto中添加如下定義。

          // demo/proto/book/book.proto

          // ...省略...

          service BookService{
          rpc Create(book)returns(book);
          }

          然后在 protoc 的編譯命令添加 gRPC相關(guān)輸出的參數(shù),完整命令如下。

          protoc \
          --proto_path=proto \
          --go_out=proto \
          --go_opt=paths=source_relative 
          --go-grpc_out=proto \
          --go-grpc_opt=paths=source_relative 
          book/book.proto book/price.proto author/author.proto

          上述命令就會生成book_grpc.pb.go文件。

          demo
          └── proto
              ├── author
              │   ├── author.pb.go
              │   └── author.proto
              └── book
                  ├── book.pb.go
                  ├── book.proto
                  ├── book_grpc.pb.go
                  ├── price.pb.go
                  └── price.proto

          gRPC-Gateway

          gRPC-Gateway 也是日常開發(fā)中比較常用的一個工具,它同樣也是根據(jù) protobuf 生成相應的代碼。

          安裝工具

          go get github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway

          為protobuf文件添加注釋

          我們在book.proto文件中添加如下注釋。

          // demo/proto/book/book.proto

          syntax = "proto3";

          // 聲明protobuf中的包名
          package book;

          // 聲明生成的Go代碼的導入路徑
          option go_package = "github.com/Q1mi/demo/proto/book";

          // 引入同目錄下的protobuf文件(注意起始位置為proto_path的下層)
          import "book/price.proto";
          // 引入其他目錄下的protobuf文件
          import "author/author.proto";
          // 引入google/protobuf/timestamp.proto文件
          import "google/protobuf/timestamp.proto";
          // 引入google/api/annotations.proto文件
          import "google/api/annotations.proto";

          message book {
          string title = 1;
          price price = 2;
          author.Info authorInfo = 3; // 需要帶package前綴
          // Timestamp是大寫T!大寫T!大寫T!
          google.protobuf.Timestamp date = 4; // 注意包名前綴
          }

          service BookService{
          rpc Create(book)returns(book){
          option (google.api.http) = {
          post: "/v1/book"
          body: "*"
          };
          };
          }

          此時,我們又引入了google/api/annotations.proto 這個文件,這個文件在https://github.com/googleapis/googleapis。我們可以像上面引入timestamp.proto文件一樣將這個庫下載到本地然后通過--proto_path指定,或者把用到的 protobuf 文件拷貝到我們的項目中。

          這里就把此處用到的google/api/annotations.proto文件和http.proto文件拷貝到項目的google/api目錄下(annotations.proto文件中引入了http.proto文件)。

          此時的項目目錄如下:

          demo
          └── proto
              ├── author
              │   ├── author.pb.go
              │   └── author.proto
              ├── book
              │   ├── book.pb.go
              │   ├── book.proto
              │   ├── book_grpc.pb.go
              │   ├── price.pb.go
              │   └── price.proto
              └── google
                  └── api
                      ├── annotations.proto
                      └── http.proto

          編譯

          這一次編譯命令在之前的基礎(chǔ)上要繼續(xù)加上 gRPC-Gateway相關(guān)的 --grpc-gateway_out=proto --grpc-gateway_opt=paths=source_relative 參數(shù)。

          完整的編譯命令如下:

          protoc \
          --proto_path=proto \
          --go_out=proto \
          --go_opt=paths=source_relative \
          --go-grpc_out=proto \
          --go-grpc_opt=paths=source_relative \
          --grpc-gateway_out=proto \
          --grpc-gateway_opt=paths=source_relative \
          book/book.proto book/price.proto author/author.proto

          最終會編譯得到一個book.pb.gw.go文件。

          demo
          └── proto
              ├── author
              │   ├── author.pb.go
              │   └── author.proto
              ├── book
              │   ├── book.pb.go
              │   ├── book.pb.gw.go
              │   ├── book.proto
              │   ├── book_grpc.pb.go
              │   ├── price.pb.go
              │   └── price.proto
              └── google
                  └── api
                      ├── annotations.proto
                      └── http.proto

          為了方便編譯可以在項目下定義Makefile

          .PHONY: gen help

          PROTO_DIR=proto

          gen:
           protoc \
           --proto_path=$(PROTO_DIR) \
           --go_out=$(PROTO_DIR) \
           --go_opt=paths=source_relative \
           --go-grpc_out=$(PROTO_DIR) \
           --go-grpc_opt=paths=source_relative \
           --grpc-gateway_out=$(PROTO_DIR) \
           --grpc-gateway_opt=paths=source_relative \
           $(shell find $(PROTO_DIR) -iname "*.proto")

          help:
           @echo "make gen - 生成pb及grpc代碼"

          后續(xù)想要編譯只需在項目目錄下執(zhí)行make gen即可。

          管理 protobuf

          在企業(yè)的項目開發(fā)中,我們通常會把 protobuf 文件存儲到一個單獨的代碼庫中,并在具體項目中通過git submodule引入。這樣做的好處是能夠?qū)?protobuf 文件統(tǒng)一管理和維護,避免因 protobuf 文件改動導致的問題。


          本文示例代碼已上傳至github倉庫:https://github.com/Q1mi/demo,請點擊查看完整代碼。



          推薦閱讀


          福利

          我為大家整理了一份從入門到進階的Go學習資料禮包,包含學習建議:入門看什么,進階看什么。關(guān)注公眾號 「polarisxu」,回復 ebook 獲取;還可以回復「進群」,和數(shù)萬 Gopher 交流學習。

          瀏覽 30
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩高消免费AV | 日韩三级黄色网 | 豆花AV网站在线观看 | 日逼综合网 | 久久国产视频播放 |