protobuf進階

BigSun丶發表於2024-03-19

目錄
  • 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 服務端資料多,客戶端資料少
  • 四、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
  • 五、巢狀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 編碼中,找到更多"序列化訊息時各種型別如何編碼"的資訊。
  1. 在java中,無符號32位和64位整型被表示成他們的整型對應形似,最高位被儲存在標誌位中。
  2. 對於所有的情況,設定值會執行型別檢查以確保此值是有效。
  3. 64位或者無符號32位整型在解碼時被表示成為ilong,但是在設定時可以使用int型值設定,在所有的情況下,值必須符合其設定其型別的要求。
  4. python中string被表示成在解碼時表示成unicode。但是一個ASCIIstring可以被表示成str型別。
  5. 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)
}

相關文章