您好,登錄后才能下訂單哦!
這篇文章主要講解了“Go中的gRPC怎么用”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“Go中的gRPC怎么用”吧!
grpc
golang-grpc 包提供了 gRPC 相關(guān)的代碼庫,通過這個(gè)庫我們可以創(chuàng)建 gRPC 服務(wù)或客戶端,首先需要安裝他。
go get -u google.golang.org/grpc
協(xié)議插件
要玩 gRPC,自然離不開 proto 文件,需要安裝兩個(gè)包,用于支持 protobuf 文件的處理。
go get -u github.com/golang/protobuf go get -u github.com/golang/protobuf/protoc-gen-go
注:GOPATH/bin
下有個(gè) protoc-gen-go.exe
文件,然而這個(gè)只是 protoc 的插件,他本身不是 protoc 工具。。。
Protocol Buffers
Protocol Buffers 是一個(gè)與編程語言無關(guān)、與平臺(tái)無關(guān)的可拓展機(jī)制,用于序列化結(jié)構(gòu)數(shù)據(jù),是一種數(shù)據(jù)交換格式,gRPC 使用 protoc 作為協(xié)議處理工具。
學(xué)習(xí) Go 的 gRPC 時(shí),有個(gè)坑,很多文章里面都沒有說到要安裝這個(gè),執(zhí)行命令提示不存在 protoc 命令。
解壓后,復(fù)制里面的 bin\protoc.exe
文件,復(fù)制到 GOPATH\bin
命令,跟 protoc-gen-go.exe
放一起。
測(cè)試
以上都妥當(dāng)后,我們?cè)谝粋€(gè)新的目錄,創(chuàng)建一個(gè) test.proto 文件,其內(nèi)容示例如下如下:
注:protoc-3.15.6-win64\include\google\protobuf
目錄也有很多示例。
syntax = "proto3"; // 包名 package test; // 指定輸出 go 語言的源碼到哪個(gè)目錄以及文件名稱 // 最終在 test.proto 目錄生成 test.pb.go // 也可以只填寫 "./" option go_package = "./;test"; // 如果要輸出其它語言的話 // option csharp_package="MyTest"; service Tester{ rpc MyTest(Request) returns (Response){} } // 函數(shù)參數(shù) message Request{ string jsonStr = 1; } // 函數(shù)返回值 message Response{ string backJson = 1; }
然后在 proto 所在目錄,執(zhí)行命令將 proto 轉(zhuǎn)換為相應(yīng)的編程語言文件。
protoc --go_out=plugins=grpc:. *.proto
會(huì)發(fā)現(xiàn)在當(dāng)前目錄輸出了 test.pb.go
文件。
創(chuàng)建一個(gè) go 程序,把 test.pb.go 復(fù)制放到在 main.go 目錄,在 main.go 引入 grpc:
import ( "context" "fmt" "google.golang.org/grpc" // test.pb.go 默認(rèn)包名是 package 為 main,不需要在這里引入 "google.golang.org/grpc/reflection" "log" "net" )
在 test.pb.go
中,生成了兩個(gè)個(gè) Tester
的接口,我們來看一下這兩個(gè)接口的定義:
type TesterServer interface { MyTest(context.Context, *Request) (*Response, error) } type TesterClient interface { MyTest(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) }
要實(shí)現(xiàn) proto 中的服務(wù),則需要我們實(shí)現(xiàn) TesterServer
接口,要編寫 客戶端,則需要實(shí)現(xiàn) TesterClient
。
這里我們先實(shí)現(xiàn) Server。
// 用于實(shí)現(xiàn) Tester 服務(wù) type MyGrpcServer struct{} func (myserver *MyGrpcServer) MyTest(context context.Context, request *Request) (*Response, error) { fmt.Println("收到一個(gè) grpc 請(qǐng)求,請(qǐng)求參數(shù):", request) response := Response{BackJson: `{"Code":666}`} return &response, nil }
接著我們創(chuàng)建 gRPC 服務(wù)。
func main() { // 創(chuàng)建 Tcp 連接 listener, err := net.Listen("tcp", ":8028") if err != nil { log.Fatalf("監(jiān)聽失敗: %v", err) } // 創(chuàng)建gRPC服務(wù) grpcServer := grpc.NewServer() // Tester 注冊(cè)服務(wù)實(shí)現(xiàn)者 // 此函數(shù)在 test.pb.go 中,自動(dòng)生成 RegisterTesterServer(grpcServer, &MyGrpcServer{}) // 在 gRPC 服務(wù)上注冊(cè)反射服務(wù) // func Register(s *grpc.Server) reflection.Register(grpcServer) err = grpcServer.Serve(listener) if err != nil { log.Fatalf("failed to serve: %v", err) } }
創(chuàng)建一個(gè)新的 go 項(xiàng)目,把 test.pb.go 復(fù)制放到 main.go 同級(jí)目錄,main.go 的代碼:
package main import ( "bufio" "context" "google.golang.org/grpc" "log" "os" ) func main() { conn, err := grpc.Dial("127.0.0.1:8028", grpc.WithInsecure()) if err != nil { log.Fatal("連接 gPRC 服務(wù)失敗,", err) } defer conn.Close() // 創(chuàng)建 gRPC 客戶端 grpcClient := NewTesterClient(conn) // 創(chuàng)建請(qǐng)求參數(shù) request := Request{ JsonStr: `{"Code":666}`, } reader := bufio.NewReader(os.Stdin) for { // 發(fā)送請(qǐng)求,調(diào)用 MyTest 接口 response, err := grpcClient.MyTest(context.Background(), &request) if err != nil { log.Fatal("發(fā)送請(qǐng)求失敗,原因是:", err) } log.Println(response) reader.ReadLine() } }
由于創(chuàng)建的時(shí)候,test.pb.go 使用的包名是 main,所以在編譯時(shí),需要把多個(gè) go 文件一起編譯:
go build .\main.go .\test.pb.go
然后分別啟動(dòng) server 和 client,在 client 每按下一次回車鍵,便發(fā)送一次 gRPC 消息。
到這里,我們學(xué)習(xí)了一個(gè)完整的 gRPC 從創(chuàng)建協(xié)議到創(chuàng)建服務(wù)和客戶端的過程,下面將接著學(xué)習(xí)一些相關(guān)的知識(shí),了解一些細(xì)節(jié)。
proto.Marshal
可以對(duì)請(qǐng)求的參數(shù)進(jìn)行序列化,如:
// 創(chuàng)建請(qǐng)求參數(shù) request := Request{ JsonStr: `{"Code":666}`, } out,err:= proto.Marshal(&request) if err != nil { log.Fatalln("Failed to encode address book:", err) } if err := ioutil.WriteFile("E:/log.txt", out, 0644); err != nil { log.Fatalln("Failed to write address book:", err) }
而 proto.Unmarshal
則可以反序列化。
我們還可以自定義如何序列化反序列化消息,代碼示例:
b, err := MarshalOptions{Deterministic: true}.Marshal(m)
Protobuf buffer 是一種數(shù)據(jù)格式,而 Protobuf 是 gRPC 協(xié)議,這里需要區(qū)分一下。
protobuf buffer 是 Google 用于序列化結(jié)構(gòu)話數(shù)據(jù)的開源機(jī)制,要定義一個(gè) protobuf buffer,需要使用 message 定義。
message Person { string name = 1; int32 id = 2; bool has_ponycopter = 3; }
開源看到,每個(gè)字段都有一個(gè) 數(shù)字, = 1
這個(gè)不是賦值,而是編號(hào)。一個(gè) message 中,每個(gè)字段都有唯一的編號(hào),這些數(shù)字用于標(biāo)識(shí)二進(jìn)制格式的字段(數(shù)據(jù)傳輸時(shí)會(huì)被壓縮等),當(dāng)編號(hào)范圍是 1-15 時(shí),存儲(chǔ)編號(hào)需要一個(gè)字節(jié),也就是說 message 中的字段盡量不超過 15 個(gè),1-15 編號(hào)用來定義頻繁出現(xiàn)的消息元素。當(dāng)然,也可以使用16-2047
之間的數(shù)字作為編號(hào),此時(shí)存儲(chǔ)編號(hào)需要兩個(gè)字節(jié)。
詳細(xì)的說可以參考官方文檔:
https://developers.google.com/protocol-buffers/docs/overview
字段類型就不詳細(xì)列表了,讀者可以參考官方文檔,這里列一下常用的數(shù)據(jù)類型:
double、float、int32、int64、bool、string、bytes、枚舉。
由于 gRPC 需要考慮兼容 C 語言、C#、Java、Go 語言等,所以 gRPC 中的類型不等同于編程語言中的相關(guān)類型。這些類型都是 gRPC 中定義的,并且如果要轉(zhuǎn)換為編程語言中的類型,需要一些轉(zhuǎn)換機(jī)制,而這有時(shí)會(huì)十分麻煩。
每個(gè)字段都可以指定一個(gè)規(guī)則,在定義字段類型的開頭使用規(guī)則標(biāo)識(shí)。
有以下三種規(guī)則:
required
:格式正確的消息必須恰好具有此字段之一,即必填字段。
optional
:格式正確的消息可以包含零個(gè)或一個(gè)此字段(但不能超過一個(gè),即值是可選的。
repeated
:在格式正確的消息中,此字段可以重復(fù)任意次(包括零次),重復(fù)值的順序?qū)⒈A簦硎驹撟侄慰梢园?~N個(gè)元素。
由于歷史原因,repeated
標(biāo)量數(shù)字類型的字段編碼效率不高。新代碼應(yīng)使用特殊選項(xiàng)[packed=true]
來獲得更有效的編碼。例如:
repeated int32 samples = 4 [packed=true];
在可選字段中 optional 中,我們可以為其設(shè)置一個(gè)默認(rèn)值,當(dāng)傳遞消息時(shí)如果沒有填寫此字段,則使用其默認(rèn)值:
optional int32 result_per_page = 3 [default = 10];
接下來將介紹 gRPC 的協(xié)議格式(protobuf),下面是官方文檔的一個(gè)示例:
syntax = "proto3"; package tutorial; import "google/protobuf/timestamp.proto";
syntax 指明協(xié)議的版本;
package 指明該 .proto 的名稱;
import 關(guān)鍵字可以在當(dāng)前 .proto 中引入其它 .proto 文件,gRPC 基本數(shù)據(jù)類型中不包含時(shí)間格式,可以引入 timestamp.proto
。
不同編程語言引入包/庫的方式是不同的,C++ 和 C# 都是使用命名空間區(qū)分代碼位置;Java 以目錄、公共類嚴(yán)格區(qū)別包名;go 則是以一個(gè) .go 文件任意設(shè)置 package 名稱。
前面提到了 protoc,可以將協(xié)議文件轉(zhuǎn)為為具體的代碼。
為了兼容各種編程語言,我們協(xié)議設(shè)置 _package
,這樣可以支持生成不同語言代碼時(shí)設(shè)置包/庫名稱。
例如 :
option go_package = "Test"; // ... option csharp_package = "MyGrpc.Protos"; // 生成命名空間 namespace MyGrpc.Protos{} option java_paclage = "MyJava.Protos"; // ...
protobuf 中除了可以定義 message,也可以定義流式接口。
gRPC使您可以定義四種服務(wù)方法:
一元 RPC,客戶端向服務(wù)器發(fā)送單個(gè)請(qǐng)求并獲得單個(gè)響應(yīng),就像普通的函數(shù)調(diào)用一樣。前面我們提到的都是一元 gRPC。
rpc SayHello(HelloRequest) returns (HelloResponse);
服務(wù)器流式RPC,客戶端在其中向服務(wù)器發(fā)送請(qǐng)求,并獲取流以讀取回一系列消息??蛻舳藦姆祷氐牧髦凶x取,直到?jīng)]有更多消息為止。gRPC保證在單個(gè)RPC調(diào)用中對(duì)消息進(jìn)行排序。
客戶端 -> 服務(wù)端 -> 返回流 -> 客戶端 -> 接收流
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
客戶端流式RPC,客戶端在其中編寫消息序列,然后再次使用提供的流將其發(fā)送到服務(wù)器??蛻舳藢懲晗⒑?,它將等待服務(wù)器讀取消息并返回其響應(yīng)。gRPC再次保證了在單個(gè)RPC調(diào)用中的消息順序。
客戶端 -> 發(fā)送流 -> 服務(wù)端 -> 接收流 ->
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
雙向流式RPC,雙方都使用讀寫流發(fā)送一系列消息。這兩個(gè)流獨(dú)立運(yùn)行,因此客戶端和服務(wù)器可以按照自己喜歡的順序進(jìn)行讀寫:例如,服務(wù)器可以在寫響應(yīng)之前等待接收所有客戶端消息,或者可以先讀取消息再寫入消息,或讀寫的其他組合。每個(gè)流中的消息順序都會(huì)保留。
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);
前面我們用 protoc 來編譯 .proto 文件為 go 語言,為了支持編譯為 go,需要安裝 protoc-gen-go
插件,C# 可以安裝 protoc-gen-zsharp
插件。
需要注意的是,轉(zhuǎn)換 .proto 為編程語言,不一定要安裝 protoc。
例如 C# 只需要把 .proto 文件放到項(xiàng)目中,通過包管理器安裝一個(gè)庫,就會(huì)自動(dòng)轉(zhuǎn)換為相應(yīng)的代碼。
回歸正題,聊一下 protoc 編譯 .proto 文件的命令。
protoc 常用的參數(shù)如下:
--proto_path=. #指定proto文件的路徑,填寫 . 表示就在當(dāng)前目錄下 --go_out=. #表示編譯后的文件存放路徑;如果編譯的是 csharp,則 --csharp_out --go_opt={xxx.proto}={xxx.proto的路徑} # 示例:--go_opt=Mprotos/bar.proto=example.com/project/protos/foo
最簡單的編譯命令:
protoc --go_out=. *.proto
--{xxx}_out
指令是必須的,因?yàn)橐敵鼍唧w的編程語言代碼。
這個(gè)輸出文件的路徑是執(zhí)行命令的路徑,如果我們不在 .proto 文件目錄下執(zhí)行命令,則輸出的代碼便不是相同位置了。為了解決這個(gè)問題,我們可以使用:
--go_opt=paths=source_relative
這樣在別的地方執(zhí)行命令,生成的代碼會(huì)跟 .proto 文件放在相同的位置。
感謝各位的閱讀,以上就是“Go中的gRPC怎么用”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對(duì)Go中的gRPC怎么用這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。