目錄
- protobuf進階
- 一、protobuf 基本型別和預設值
- 1.1 protobuf型別和語言對應關係
- 1.2 protobuf預設值
- 3. 案例
- (1)目錄結構
- (2)hello.proto
- (3)生成go檔案
- (4)client/main.go
- (5)server/main.go
- 二、option go_package的作用
- 三、服務端客戶端同步問題
- 3.1 順序導致的錯誤
- (1)目錄結構
- (2)server/proto/hello.proto
- (3)client/proto/hello.proto
- (4)生成go檔案
- (5)client/main.go
- (6)server/main.go
- 3.2 服務端資料多,客戶端資料少
- 3.1 順序導致的錯誤
- 四、import另一個proto
- 4.1 引入自定義的proto
- (1)目錄結構
- (2)proto/order.proto
- (3)oproto/order.proto
- (4)命令生成go檔案
- (5)client/main.go
- (5)server/main.go
- 4.2 引入內建的proto
- (1)proto/order.proto
- (2)oproto/order.proto
- (3)命令生成go檔案
- (4)client/main.go
- (6)server/main.go
- 4.1 引入自定義的proto
- 五、巢狀message物件
- (1)order.proto
- (2)命令生成go檔案
- (3)client/main.go
- (4)server/main.go
- 六、enum列舉型別
- (1)命令生成go檔案
- (2)server/main.go
- (3)client/main.go
- 七、map型別
- (1)order.proto
- (2)命令生成go檔案
- (3)server/main.go
- (4)client/main.go
- 八、內建的timestamp型別
- (1)order.proto
- (2)命令生成go檔案
- (3)server/main.go
- (4)client/main.go
protobuf進階
- 官方地址: https://developers.google.com/protocol-buffers/docs/proto3
一、protobuf 基本型別和預設值
1.1 protobuf型別和語言對應關係
- 該表格展示了定義於.proto檔案中的型別,與go和python對應的型別:
.proto Type | Notes | Python Type | Go Type |
---|---|---|---|
double | float | float64 | |
float | float | float32 | |
int32 | 使用變長編碼,對於負值的效率很低,如果你的域有可能有負值,請使用sint64替代 | int | int32 |
uint32 | 使用變長編碼 | int | uint32 |
uint64 | 使用變長編碼 | int | uint64 |
sint32 | 使用變長編碼,這些編碼在負值時比int32高效的多 | int | int32 |
sint64 | 使用變長編碼,有符號的整型值。編碼時比通常的int64高效。 | int | int64 |
fixed32 | 總是4個位元組,如果數值總是比總是比228大的話,這個型別會比uint32高效。 | int | uint32 |
fixed64 | 總是8個位元組,如果數值總是比總是比256大的話,這個型別會比uint64高效。 | int | uint64 |
sfixed32 | 總是4個位元組 | int | int32 |
sfixed64 | 總是8個位元組 | int | int64 |
bool | bool | bool | |
string | 一個字串必須是UTF-8編碼或者7-bit ASCII編碼的文字。 | str | string |
bytes | 可能包含任意順序的位元組資料。 | str | []byte |
- 可以在文章Protocol Buffer 編碼中,找到更多"序列化訊息時各種型別如何編碼"的資訊。
- 在java中,無符號32位和64位整型被表示成他們的整型對應形似,最高位被儲存在標誌位中。
- 對於所有的情況,設定值會執行型別檢查以確保此值是有效。
- 64位或者無符號32位整型在解碼時被表示成為ilong,但是在設定時可以使用int型值設定,在所有的情況下,值必須符合其設定其型別的要求。
- python中string被表示成在解碼時表示成unicode。但是一個ASCIIstring可以被表示成str型別。
- Integer在64位的機器上使用,string在32位機器上使用
1.2 protobuf預設值
-
如果protobuf定義了型別,在gRPC使用過程中沒有傳值,會使用預設值
-
當一個訊息被解析的時候,如果被編碼的資訊不包含一個特定的元素,被解析的物件鎖對應的域被設定位一個預設值,對於不同型別指定如下:
-
對於strings,預設是一個空string
-
對於bytes,預設是一個空的bytes
-
對於bools,預設是false
-
對於數值型別,預設是0
-
對於列舉,預設是第一個定義的列舉值,必須為0;
-
對於訊息型別(message),域沒有被設定,確切的訊息是根據語言確定的,詳見generated code guide
對於可重複域的預設值是空(通常情況下是對應語言中空列表)。
注:對於標量訊息域,一旦訊息被解析,就無法判斷域釋放被設定為預設值(例如,例如boolean值是否被設定為false)還是根本沒有被設定。你應該在定義你的訊息型別時非常注意。例如,比如你不應該定義boolean的預設值false作為任何行為的觸發方式。也應該注意如果一個標量訊息域被設定為標誌位,這個值不應該被序列化傳輸。
檢視generated code guide選擇你的語言的預設值的工作細節
3. 案例
(1)目錄結構
proto_default_demo
-client
main.go
-proto
hello.proto
-server
main.go
(2)hello.proto
syntax = "proto3";
option go_package = ".;proto";
// 定義一個服務,gRPC自有的,它需要用grpc外掛生成,也就是咱們安裝的那個外掛
service Hello{
// 服務內有一個函式叫Hello,接收HelloRequest型別引數,返回HelloResponse型別引數
rpc Hello(HelloRequest) returns(HelloResponse);
}
// 類似於go的結構體,可以定義屬性
message HelloRequest {
string name = 1; // 1 是編號,不是值
int32 age = 2;
repeated string girls = 3;
}
// 定義一個響應的型別
message HelloResponse {
string reply =1;
}
(3)生成go檔案
//protoc --go_out=. --go_opt=paths=source_relative ./hello.proto
//protoc --go-grpc_out=. --go-grpc_opt=require_unimplemented_servers=false --go-grpc_opt=paths=source_relative ./hello.proto
(4)client/main.go
package main
import (
"context"
"fmt"
"go_test_learn/proto_default_demo/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func main() {
conn, err := grpc.Dial("127.0.0.1:50052", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic("連線服務異常")
}
defer conn.Close()
client := proto.NewHelloClient(conn)
request := proto.HelloRequest{} // 不傳值,看服務端列印
res, err := client.Hello(context.Background(), &request)
if err != nil {
panic("呼叫方法異常")
}
//列印出返回的資料
fmt.Println(res.Reply)
}
(5)server/main.go
package main
import (
"context"
"fmt"
"go_test_learn/proto"
"google.golang.org/grpc"
"net"
)
type HelloServer struct {
}
func (s *HelloServer) Hello(context context.Context, request *proto.HelloRequest) (*proto.HelloResponse, error) {
fmt.Println(request.Name) //此處列印出預設值
return &proto.HelloResponse{Reply:"收到客戶端的訊息為:"+request.Name}, nil
}
func main() {
g := grpc.NewServer()
s := HelloServer{}
proto.RegisterHelloServer(g,&s)
lis,error:=net.Listen("tcp","0.0.0.0:50052")
if error!=nil{
panic("啟動服務異常")
}
g.Serve(lis)
}
二、option go_package的作用
- 可以為.proto檔案新增一個可選的package宣告符,用來防止不同的訊息型別有命名衝突,區分語言,go_package為go語言的定義
// 基本使用
option go_package = ".;proto";
// 這樣會把go檔案生成到當前路徑,並且go檔案的包名為proto
// 指定生成的go檔案放到某個路徑下
option go_package = "common/hello/proto/v1";
// 這樣會把go檔案生成到當前路徑下的common/hello/proto/資料夾中,包名為v1
// 使用命令生成
protoc --go_out=. ./hello.proto
protoc --go-grpc_out=. --go-grpc_opt=require_unimplemented_servers=false ./hello.proto
// 指定生成的go檔案放到某個路徑下
option go_package = "../../common/hello/proto/v1";
// 這樣會把go檔案生成到當前路徑下的上兩級目錄的common/hello/proto/資料夾中,包名為v1
// 這樣便於以後公共的生成到一起,多個微服務共用同樣的檔案
// 使用命令生成
protoc --go_out=. ./hello.proto
protoc --go-grpc_out=. --go-grpc_opt=require_unimplemented_servers=false ./hello.proto
三、服務端客戶端同步問題
- 因為客戶端和服務端要使用同一個proto檔案,可能是兩個人寫的,如果兩個proto檔案內容不一致,會導致錯誤
3.1 順序導致的錯誤
(1)目錄結構
dif_proto
-server
-main.go
-proto
-hello.proto // 該檔案應該和client下的檔案完全一致
-client
-main.go
-proto
-hello.proto // 該檔案應該和server下的檔案完全一致
(2)server/proto/hello.proto
syntax = "proto3";
option go_package = ".;proto";
service Hello{
rpc Hello(HelloRequest) returns(HelloResponse);
}
message HelloRequest {
string name = 1; // 服務端編號為 name是1,gender 是2
string gender = 2;
}
message HelloResponse {
string reply =1;
}
(3)client/proto/hello.proto
syntax = "proto3";
option go_package = ".;proto";
service Hello{
rpc Hello(HelloRequest) returns(HelloResponse);
}
message HelloRequest {
// 客戶端編號與服務端編號順序不一致 name是2,gender 是1
string gender = 1;
string name = 2;
}
// 定義一個響應的型別
message HelloResponse {
string reply =1;
}
(4)生成go檔案
cd dif_proto/client/proto
//protoc --go_out=. ./hello.proto
//protoc --go-grpc_out=. --go-grpc_opt=require_unimplemented_servers=false ./hello.proto
cd dif_proto/server/proto
//protoc --go_out=. ./hello.proto
//protoc --go-grpc_out=. --go-grpc_opt=require_unimplemented_servers=false ./hello.proto
(5)client/main.go
package main
import (
"context"
"fmt"
"go_test_learn/dif_proto/client/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func main() {
conn, err := grpc.Dial("127.0.0.1:50052", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic("連線服務異常")
}
defer conn.Close()
client := proto.NewHelloClient(conn)
request := proto.HelloRequest{
Name: "lqz",
Gender: "男",
}
res, err := client.Hello(context.Background(), &request)
if err != nil {
panic("呼叫方法異常")
}
//列印出返回的資料
fmt.Println(res.Reply)
}
(6)server/main.go
package main
import (
"context"
"fmt"
"go_test_learn/dif_proto/server/proto"
"google.golang.org/grpc"
"net"
)
type HelloServer struct {
}
func (s *HelloServer) Hello(context context.Context, request *proto.HelloRequest) (*proto.HelloResponse, error) {
fmt.Println("name:",request.Name)
fmt.Println("gender:",request.Gender)
return &proto.HelloResponse{Reply:"ok"}, nil
}
func main() {
g := grpc.NewServer()
s := HelloServer{}
proto.RegisterHelloServer(g,&s)
lis,error:=net.Listen("tcp","0.0.0.0:50052")
if error!=nil{
panic("啟動服務異常")
}
g.Serve(lis)
}
3.2 服務端資料多,客戶端資料少
- 這樣不客戶端和服務端都能執行,只是資料會少
//client/proto/hello.proto
message HelloRequest {
string name = 1;
string gender = 2;
}
//server/proto/hello.proto
message HelloRequest {
string name = 1;
}
// 重新命令生成go檔案,重新執行客戶端和服務端
// 程式正常執行
四、import另一個proto
4.1 引入自定義的proto
(1)目錄結構
// 目錄結構
import_proto
-client
-main.go
-server
-main.go
-proto
-order.proto
-base.proto
(2)proto/order.proto
syntax = "proto3";
import "base.proto"; // 匯入另一個proto,就可以使用裡面的Empty和Pong了
option go_package = ".;proto";
service Hello{
rpc Hello(HelloRequest) returns(HelloResponse);
// 因為 rpc的服務必須要傳一個引數,但是有時候我們不需要傳引數,所以定義一個Empty的message
rpc Ping(Empty) returns(Pong);
}
message HelloRequest {
string name = 1;
}
message HelloResponse {
string reply =1;
}
(3)oproto/order.proto
syntax = "proto3";
option go_package = ".;proto"; // 此處不要忘了加入包的宣告
message Empty {
}
// 定義一個Pong
message Pong {
int32 code =1;
}
(4)命令生成go檔案
// 切換到相應路徑下
protoc --go_out=. ./order.proto
protoc --go-grpc_out=. --go-grpc_opt=require_unimplemented_servers=false ./order.proto
// base.proto也要生成
protoc --go_out=. ./base.proto
(5)client/main.go
package main
import (
"context"
"fmt"
"go_test_learn/import_proto/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func main() {
conn, err := grpc.Dial("127.0.0.1:50052", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic("連線服務異常")
}
defer conn.Close()
client := proto.NewHelloClient(conn)
request := proto.Empty{}
res, err := client.Ping(context.Background(), &request)
if err != nil {
panic("呼叫方法異常")
}
//列印出返回的資料
fmt.Println(res.Code)
}
(5)server/main.go
package main
import (
"context"
"fmt"
"go_test_learn/import_proto/proto"
"google.golang.org/grpc"
"net"
)
type HelloServer struct {
}
func (s *HelloServer) Hello(context context.Context, request *proto.HelloRequest) (*proto.HelloResponse, error) {
fmt.Println(request.Name) //此處列印出預設值
return &proto.HelloResponse{Reply: "收到客戶端的訊息為:" + request.Name}, nil
}
func (s *HelloServer) Ping(context context.Context, request *proto.Empty) (*proto.Pong, error) {
fmt.Println(request) //request是Empty的物件,沒有值
return &proto.Pong{Code: 100}, nil
}
func main() {
g := grpc.NewServer()
s := HelloServer{}
proto.RegisterHelloServer(g, &s)
lis, error := net.Listen("tcp", "0.0.0.0:50052")
if error != nil {
panic("啟動服務異常")
}
g.Serve(lis)
}
4.2 引入內建的proto
(1)proto/order.proto
syntax = "proto3";
import "base.proto"; // 匯入另一個proto,就可以使用裡面的Empty和Pong了
// 引入谷歌提供的,必須要使用protoc/include/google/protobuf內帶empty.proto檔案,否則報錯
import "google/protobuf/empty.proto"; // 谷歌內建了empty給咱們用,按住control可以看原始碼,內有go_package是go包匯入路徑
option go_package = ".;proto";
service Hello{
rpc Hello(HelloRequest) returns(HelloResponse);
// 因為 rpc的服務必須要傳一個引數,但是有時候我們不需要傳引數,所以定義一個Empty的message
// rpc Ping(Empty) returns(Pong);
// 此處使用必須用google.protobuf.Empty
rpc Ping(google.protobuf.Empty) returns(Pong);
}
message HelloRequest {
string name = 1;
}
message HelloResponse {
string reply = 1;
}
(2)oproto/order.proto
syntax = "proto3";
option go_package = ".;proto"; // 此處不要忘了加入包的宣告
// 定義一個Pong
message Pong {
int32 code =1;
}
(3)命令生成go檔案
// 切換到相應路徑下
protoc --go_out=. ./order.proto
protoc --go-grpc_out=. --go-grpc_opt=require_unimplemented_servers=false ./order.proto
// base.proto也要生成
protoc --go_out=. ./base.proto
// 刪除已下載的模組快取
go clean --modcache
(4)client/main.go
package main
import (
"context"
"fmt"
"github.com/golang/protobuf/ptypes/empty" // empty使用這個包下的
"go_test_learn/import_proto/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func main() {
conn, err := grpc.Dial("127.0.0.1:50052", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic("連線服務異常")
}
defer conn.Close()
client := proto.NewHelloClient(conn)
request := empty.Empty{}
res, err := client.Ping(context.Background(), &request)
if err != nil {
panic("呼叫方法異常")
}
//列印出返回的資料
fmt.Println(res.Code)
}
(6)server/main.go
package main
import (
"context"
"fmt"
"github.com/golang/protobuf/ptypes/empty" // empty使用這個包下的
"go_test_learn/import_proto/proto"
"google.golang.org/grpc"
"net"
)
type HelloServer struct {
}
func (s *HelloServer) Hello(context context.Context, request *proto.HelloRequest) (*proto.HelloResponse, error) {
fmt.Println(request.Name) //此處列印出預設值
return &proto.HelloResponse{Reply: "收到客戶端的訊息為:" + request.Name}, nil
}
func (s *HelloServer) Ping(context context.Context, request *empty.Empty) (*proto.Pong, error) {
fmt.Println(request) //request是Empty的物件,沒有值
return &proto.Pong{Code: 100}, nil
}
func main() {
g := grpc.NewServer()
s := HelloServer{}
proto.RegisterHelloServer(g, &s)
lis, error := net.Listen("tcp", "0.0.0.0:50052")
if error != nil {
panic("啟動服務異常")
}
g.Serve(lis)
}
五、巢狀message物件
- proto檔案的message可以巢狀其他的message,修改order.proto如下
(1)order.proto
syntax = "proto3";
import "base.proto"; // 匯入另一個proto,就可以使用裡面的Empty和Pong了
import "google/protobuf/empty.proto"; // 谷歌內建了empty給咱們用,按住control可以看原始碼,內有go_package是go包匯入路徑
option go_package = ".;proto";
service Hello{
rpc Hello(HelloRequest) returns(HelloResponse);
// 因為 rpc的服務必須要傳一個引數,但是有時候我們不需要傳引數,所以定義一個Empty的message
// rpc Ping(Empty) returns(Pong);
// 此處使用必須用google.protobuf.Empty
rpc Ping(google.protobuf.Empty) returns(Pong);
}
message HelloRequest {
string name = 1;
}
message HelloResponse {
message Result { // 巢狀Result,也可以放在message HelloResponse外面
string code = 1;
string msg = 2;
}
string reply = 1;
Result data = 2;
}
(2)命令生成go檔案
// 切換到相應路徑下
protoc --go_out=. ./order.proto
protoc --go-grpc_out=. --go-grpc_opt=require_unimplemented_servers=false ./order.proto
// base.proto也要生成
protoc --go_out=. ./base.proto
(3)client/main.go
package main
import (
"context"
"fmt"
"go_test_learn/import_proto/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func main() {
conn, err := grpc.Dial("127.0.0.1:50052", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic("連線服務異常")
}
defer conn.Close()
client := proto.NewHelloClient(conn)
request := proto.HelloRequest{Name: "lqz"}
res, err := client.Hello(context.Background(), &request)
if err != nil {
panic("呼叫方法異常")
}
//message巢狀,可以巢狀獲取
fmt.Println(res.Data.Code)
fmt.Println(res.Data.Msg)
}
(4)server/main.go
package main
import (
"context"
"fmt"
"github.com/golang/protobuf/ptypes/empty" // empty使用這個包下的
"go_test_learn/import_proto/proto"
"google.golang.org/grpc"
"net"
)
type HelloServer struct {
}
func (s *HelloServer) Hello(context context.Context, request *proto.HelloRequest) (*proto.HelloResponse, error) {
fmt.Println(request.Name) //此處列印出預設值
return &proto.HelloResponse{
Reply: "收到客戶端的訊息為:" + request.Name,
Data: &proto.HelloResponse_Result{Code:"100",Msg: "成功"}, // 注意此處,巢狀的Result變成了HelloResponse_Result
}, nil
}
func (s *HelloServer) Ping(context context.Context, request *empty.Empty) (*proto.Pong, error) {
fmt.Println(request) //request是Empty的物件,沒有值
return &proto.Pong{Code: 100}, nil
}
func main() {
g := grpc.NewServer()
s := HelloServer{}
proto.RegisterHelloServer(g, &s)
lis, error := net.Listen("tcp", "0.0.0.0:50052")
if error != nil {
panic("啟動服務異常")
}
g.Serve(lis)
}
六、enum列舉型別
syntax = "proto3";
option go_package = ".;proto";
service Hello{
rpc Hello(HelloRequest) returns(HelloResponse);
}
message HelloRequest {
string name = 1;
Gender gen =2; // 使用列舉型別
}
message HelloResponse {
string reply = 1;
}
// 定義列舉型別
enum Gender{
Male = 0;
Female = 1;
}
(1)命令生成go檔案
// 切換到相應路徑下
protoc --go_out=. ./order.proto
protoc --go-grpc_out=. --go-grpc_opt=require_unimplemented_servers=false ./order.proto
(2)server/main.go
package main
import (
"context"
"fmt"
"go_test_learn/import_proto/proto"
"google.golang.org/grpc"
"net"
)
type HelloServer struct {
}
func (s *HelloServer) Hello(context context.Context, request *proto.HelloRequest) (*proto.HelloResponse, error) {
fmt.Println(request.Name) //此處列印出預設值
return &proto.HelloResponse{
Reply: "收到客戶端的性別為:" + request.Gen.String(),
}, nil
}
func main() {
g := grpc.NewServer()
s := HelloServer{}
proto.RegisterHelloServer(g, &s)
lis, error := net.Listen("tcp", "0.0.0.0:50052")
if error != nil {
panic("啟動服務異常")
}
g.Serve(lis)
}
(3)client/main.go
package main
import (
"context"
"fmt"
"go_test_learn/import_proto/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func main() {
conn, err := grpc.Dial("127.0.0.1:50052", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic("連線服務異常")
}
defer conn.Close()
client := proto.NewHelloClient(conn)
// 使用列舉型別Gen: proto.Gender_Male,本質是int32
request := proto.HelloRequest{Name: "lqz",Gen: proto.Gender_Male}
res, err := client.Hello(context.Background(), &request)
if err != nil {
panic("呼叫方法異常")
}
fmt.Println(res.Reply)
}
七、map型別
(1)order.proto
syntax = "proto3";
option go_package = ".;proto";
service Hello{
rpc Hello(HelloRequest) returns(HelloResponse);
}
message HelloRequest {
string name = 1;
Gender gen =2; // 使用列舉型別
map<string,string> info =3; // map型別,要指定key和value的型別
}
message HelloResponse {
string reply = 1;
}
// 定義列舉型別
enum Gender{
Male = 0;
Female = 1;
}
(2)命令生成go檔案
// 切換到相應路徑下
protoc --go_out=. ./order.proto
protoc --go-grpc_out=. --go-grpc_opt=require_unimplemented_servers=false ./order.proto
(3)server/main.go
package main
import (
"context"
"fmt"
"go_test_learn/import_proto/proto"
"google.golang.org/grpc"
"net"
)
type HelloServer struct {
}
func (s *HelloServer) Hello(context context.Context, request *proto.HelloRequest) (*proto.HelloResponse, error) {
fmt.Println(request.Name) //此處列印出預設值
// 取出map型別
return &proto.HelloResponse{
Reply: "收到客戶端的map:" + request.Info["name"],
}, nil
}
func main() {
g := grpc.NewServer()
s := HelloServer{}
proto.RegisterHelloServer(g, &s)
lis, error := net.Listen("tcp", "0.0.0.0:50052")
if error != nil {
panic("啟動服務異常")
}
g.Serve(lis)
}
(4)client/main.go
package main
import (
"context"
"fmt"
"go_test_learn/import_proto/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func main() {
conn, err := grpc.Dial("127.0.0.1:50052", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic("連線服務異常")
}
defer conn.Close()
client := proto.NewHelloClient(conn)
// 使用map型別,實質給對映成map[string]string
request := proto.HelloRequest{Name: "lqz",
Gen: proto.Gender_Male,
Info:map[string]string{"name":"lqz","age":"19"},
}
res, err := client.Hello(context.Background(), &request)
if err != nil {
panic("呼叫方法異常")
}
fmt.Println(res.Reply)
}
八、內建的timestamp型別
(1)order.proto
syntax = "proto3";
import "google/protobuf/timestamp.proto"; // 先引入
option go_package = ".;proto";
service Hello{
rpc Hello(HelloRequest) returns(HelloResponse);
}
message HelloRequest {
string name = 1;
Gender gen =2; // 使用列舉型別
map<string,string> info =3; // map型別,要指定key和value的型別
google.protobuf.Timestamp now =4; // 使用Timestamp時間型別
}
message HelloResponse {
string reply = 1;
}
// 定義列舉型別
enum Gender{
Male = 0;
Female = 1;
}
(2)命令生成go檔案
// 切換到相應路徑下
protoc --go_out=. ./order.proto
protoc --go-grpc_out=. --go-grpc_opt=require_unimplemented_servers=false ./order.proto
(3)server/main.go
package main
import (
"context"
"fmt"
"go_test_learn/import_proto/proto"
"google.golang.org/grpc"
"net"
)
type HelloServer struct {
}
func (s *HelloServer) Hello(context context.Context, request *proto.HelloRequest) (*proto.HelloResponse, error) {
fmt.Println(request.Name) //此處列印出預設值
// 取出map型別
return &proto.HelloResponse{
Reply: "收到客戶端的時間:" + request.Now.AsTime().String(),
}, nil
}
func main() {
g := grpc.NewServer()
s := HelloServer{}
proto.RegisterHelloServer(g, &s)
lis, error := net.Listen("tcp", "0.0.0.0:50052")
if error != nil {
panic("啟動服務異常")
}
g.Serve(lis)
}
(4)client/main.go
package main
import (
"context"
"fmt"
"go_test_learn/import_proto/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"time"
// "github.com/golang/protobuf/ptypes/timestamp" // 不匯入這個路徑
timestamppb "google.golang.org/protobuf/types/known/timestamppb" //上指向這個,直接匯入這個路徑
)
func main() {
conn, err := grpc.Dial("127.0.0.1:50052", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic("連線服務異常")
}
defer conn.Close()
client := proto.NewHelloClient(conn)
request := proto.HelloRequest{Name: "lqz",
Gen: proto.Gender_Male,
Info:map[string]string{"name":"lqz","age":"19"},
Now: timestamppb.New(time.Now()), // timestamppb有New方法,傳入Time物件即可
}
res, err := client.Hello(context.Background(), &request)
if err != nil {
panic("呼叫方法異常")
}
fmt.Println(res.Reply)
}