網站首頁 編程語言 正文
Go GRPC 入門
1,安裝包
grpc
golang-grpc 包提供了 gRPC 相關的代碼庫,通過這個庫我們可以創建 gRPC 服務或客戶端,首先需要安裝他。
go get -u google.golang.org/grpc
協議插件
要玩 gRPC,自然離不開 proto 文件,需要安裝兩個包,用于支持 protobuf 文件的處理。
go get -u github.com/golang/protobuf go get -u github.com/golang/protobuf/protoc-gen-go
注:GOPATH/bin
?下有個?protoc-gen-go.exe
?文件,然而這個只是 protoc 的插件,他本身不是 protoc 工具。。。
Protocol Buffers
Protocol Buffers 是一個與編程語言無關、與平臺無關的可拓展機制,用于序列化結構數據,是一種數據交換格式,gRPC 使用 protoc 作為協議處理工具。
學習 Go 的 gRPC 時,有個坑,很多文章里面都沒有說到要安裝這個,執行命令提示不存在 protoc 命令。
首先到?https://github.com/protocolbuffers/protobuf/releases?下載 相應的包,例如筆者下載的是?protoc-3.15.6-win64.zip
。
解壓后,復制里面的?bin\protoc.exe
?文件,復制到?GOPATH\bin
?命令,跟?protoc-gen-go.exe
?放一起。
測試
以上都妥當后,我們在一個新的目錄,創建一個 test.proto 文件,其內容示例如下如下:
注:protoc-3.15.6-win64\include\google\protobuf
?目錄也有很多示例。
syntax = "proto3"; // 包名 package test; // 指定輸出 go 語言的源碼到哪個目錄以及文件名稱 // 最終在 test.proto 目錄生成 test.pb.go // 也可以只填寫 "./" option go_package = "./;test"; // 如果要輸出其它語言的話 // option csharp_package="MyTest"; service Tester{ rpc MyTest(Request) returns (Response){} } // 函數參數 message Request{ string jsonStr = 1; } // 函數返回值 message Response{ string backJson = 1; }
然后在 proto 所在目錄,執行命令將 proto 轉換為相應的編程語言文件。
protoc --go_out=plugins=grpc:. *.proto
會發現在當前目錄輸出了?test.pb.go
?文件。
2,gRPC 服務端
創建一個 go 程序,把 test.pb.go 復制放到在 main.go 目錄,在 main.go 引入 grpc:
import ( "context" "fmt" "google.golang.org/grpc" // test.pb.go 默認包名是 package 為 main,不需要在這里引入 "google.golang.org/grpc/reflection" "log" "net" )
在?test.pb.go
?中,生成了兩個個?Tester
?的接口,我們來看一下這兩個接口的定義:
type TesterServer interface { MyTest(context.Context, *Request) (*Response, error) } type TesterClient interface { MyTest(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) }
要實現 proto 中的服務,則需要我們實現?TesterServer
?接口,要編寫 客戶端,則需要實現?TesterClient
?。
這里我們先實現 Server。
// 用于實現 Tester 服務 type MyGrpcServer struct{} func (myserver *MyGrpcServer) MyTest(context context.Context, request *Request) (*Response, error) { fmt.Println("收到一個 grpc 請求,請求參數:", request) response := Response{BackJson: `{"Code":666}`} return &response, nil }
接著我們創建 gRPC 服務。
func main() { // 創建 Tcp 連接 listener, err := net.Listen("tcp", ":8028") if err != nil { log.Fatalf("監聽失敗: %v", err) } // 創建gRPC服務 grpcServer := grpc.NewServer() // Tester 注冊服務實現者 // 此函數在 test.pb.go 中,自動生成 RegisterTesterServer(grpcServer, &MyGrpcServer{}) // 在 gRPC 服務上注冊反射服務 // func Register(s *grpc.Server) reflection.Register(grpcServer) err = grpcServer.Serve(listener) if err != nil { log.Fatalf("failed to serve: %v", err) } }
3,gRPC 客戶端
創建一個新的 go 項目,把 test.pb.go 復制放到 main.go 同級目錄,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 服務失敗,", err) } defer conn.Close() // 創建 gRPC 客戶端 grpcClient := NewTesterClient(conn) // 創建請求參數 request := Request{ JsonStr: `{"Code":666}`, } reader := bufio.NewReader(os.Stdin) for { // 發送請求,調用 MyTest 接口 response, err := grpcClient.MyTest(context.Background(), &request) if err != nil { log.Fatal("發送請求失敗,原因是:", err) } log.Println(response) reader.ReadLine() } }
4,編譯運行
由于創建的時候,test.pb.go 使用的包名是 main,所以在編譯時,需要把多個 go 文件一起編譯:
go build .\main.go .\test.pb.go
然后分別啟動 server 和 client,在 client 每按下一次回車鍵,便發送一次 gRPC 消息。
到這里,我們學習了一個完整的 gRPC 從創建協議到創建服務和客戶端的過程,下面將接著學習一些相關的知識,了解一些細節。
5,其它
proto.Marshal
?可以對請求的參數進行序列化,如:
// 創建請求參數 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)
GRPC
Protobuf buffer
Protobuf buffer 是一種數據格式,而 Protobuf 是 gRPC 協議,這里需要區分一下。
protobuf buffer 是 Google 用于序列化結構話數據的開源機制,要定義一個 protobuf buffer,需要使用 message 定義。
message Person { string name = 1; int32 id = 2; bool has_ponycopter = 3; }
開源看到,每個字段都有一個 數字,?= 1
?這個不是賦值,而是編號。一個 message 中,每個字段都有唯一的編號,這些數字用于標識二進制格式的字段(數據傳輸時會被壓縮等),當編號范圍是 1-15 時,存儲編號需要一個字節,也就是說 message 中的字段盡量不超過 15 個,1-15 編號用來定義頻繁出現的消息元素。當然,也可以使用16-2047
?之間的數字作為編號,此時存儲編號需要兩個字節。
詳細的說可以參考官方文檔:
https://developers.google.com/protocol-buffers/docs/overview
字段類型
字段類型就不詳細列表了,讀者可以參考官方文檔,這里列一下常用的數據類型:
double、float、int32、int64、bool、string、bytes、枚舉。
由于 gRPC 需要考慮兼容 C 語言、C#、Java、Go 語言等,所以 gRPC 中的類型不等同于編程語言中的相關類型。這些類型都是 gRPC 中定義的,并且如果要轉換為編程語言中的類型,需要一些轉換機制,而這有時會十分麻煩。
字段規則
每個字段都可以指定一個規則,在定義字段類型的開頭使用規則標識。
有以下三種規則:
-
required
:格式正確的消息必須恰好具有此字段之一,即必填字段。 -
optional
:格式正確的消息可以包含零個或一個此字段(但不能超過一個,即值是可選的。 -
repeated
:在格式正確的消息中,此字段可以重復任意次(包括零次),重復值的順序將保留,表示該字段可以包含0~N個元素。
由于歷史原因,repeated
標量數字類型的字段編碼效率不高。新代碼應使用特殊選項[packed=true]
來獲得更有效的編碼。例如:
repeated int32 samples = 4 [packed=true];
在可選字段中 optional 中,我們可以為其設置一個默認值,當傳遞消息時如果沒有填寫此字段,則使用其默認值:
optional int32 result_per_page = 3 [default = 10];
Protobuf
接下來將介紹 gRPC 的協議格式(protobuf),下面是官方文檔的一個示例:
syntax = "proto3"; package tutorial; import "google/protobuf/timestamp.proto";
syntax 指明協議的版本;
package 指明該 .proto 的名稱;
import 關鍵字可以在當前 .proto 中引入其它 .proto 文件,gRPC 基本數據類型中不包含時間格式,可以引入?timestamp.proto
。
不同編程語言引入包/庫的方式是不同的,C++ 和 C# 都是使用命名空間區分代碼位置;Java 以目錄、公共類嚴格區別包名;go 則是以一個 .go 文件任意設置 package 名稱。
前面提到了 protoc,可以將協議文件轉為為具體的代碼。
為了兼容各種編程語言,我們協議設置?_package
,這樣可以支持生成不同語言代碼時設置包/庫名稱。
例如 :
option go_package = "Test"; // ... option csharp_package = "MyGrpc.Protos"; // 生成命名空間 namespace MyGrpc.Protos{} option java_paclage = "MyJava.Protos"; // ...
gRPC 四種服務方法
protobuf 中除了可以定義 message,也可以定義流式接口。
gRPC使您可以定義四種服務方法:
-
一元 RPC,客戶端向服務器發送單個請求并獲得單個響應,就像普通的函數調用一樣。前面我們提到的都是一元 gRPC。
rpc SayHello(HelloRequest) returns (HelloResponse);
-
服務器流式RPC,客戶端在其中向服務器發送請求,并獲取流以讀取回一系列消息。客戶端從返回的流中讀取,直到沒有更多消息為止。gRPC保證在單個RPC調用中對消息進行排序。
客戶端 -> 服務端 -> 返回流 -> 客戶端 -> 接收流
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
-
客戶端流式RPC,客戶端在其中編寫消息序列,然后再次使用提供的流將其發送到服務器。客戶端寫完消息后,它將等待服務器讀取消息并返回其響應。gRPC再次保證了在單個RPC調用中的消息順序。
客戶端 -> 發送流 -> 服務端 -> 接收流 ->
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
-
雙向流式RPC,雙方都使用讀寫流發送一系列消息。這兩個流獨立運行,因此客戶端和服務器可以按照自己喜歡的順序進行讀寫:例如,服務器可以在寫響應之前等待接收所有客戶端消息,或者可以先讀取消息再寫入消息,或讀寫的其他組合。每個流中的消息順序都會保留。
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);
編譯 proto
前面我們用 protoc 來編譯 .proto 文件為 go 語言,為了支持編譯為 go,需要安裝?protoc-gen-go
?插件,C# 可以安裝?protoc-gen-zsharp
?插件。
需要注意的是,轉換 .proto 為編程語言,不一定要安裝 protoc。
例如 C# 只需要把 .proto 文件放到項目中,通過包管理器安裝一個庫,就會自動轉換為相應的代碼。
回歸正題,聊一下 protoc 編譯 .proto 文件的命令。
protoc 常用的參數如下:
--proto_path=. #指定proto文件的路徑,填寫 . 表示就在當前目錄下 --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
?指令是必須的,因為要輸出具體的編程語言代碼。
這個輸出文件的路徑是執行命令的路徑,如果我們不在 .proto 文件目錄下執行命令,則輸出的代碼便不是相同位置了。為了解決這個問題,我們可以使用:
--go_opt=paths=source_relative
這樣在別的地方執行命令,生成的代碼會跟 .proto 文件放在相同的位置。
原文鏈接:https://www.cnblogs.com/whuanle/p/14588031.html
相關推薦
- 2022-07-25 Python?APScheduler?定時任務詳解_python
- 2022-04-19 Android線性布局與相對布局的實現_Android
- 2022-03-29 深入了解Python并發編程_python
- 2022-02-23 圖片返回base64數據渲染為圖片的處理
- 2023-02-09 Python的Flask項目中獲取請求用戶IP地址?addr問題_python
- 2022-10-31 Kotlin中Object關鍵字的使用示例介紹_Android
- 2022-07-14 Python萬物皆對象理解及源碼學習_python
- 2022-01-17 什么是token token用在哪 token放在哪比較好
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支