Protobuf

Ho1d_F0rward發表於2024-08-24

什麼是protobuf

資料在進行網路傳輸的時候,需要進行序列化,序列化協議有很多種,比如xml, json,protobuf等
gRPC預設使用protocol buffers,這是google開源的一套成熟的結構資料序列化機制。

簡單使用

定義一種原始檔,副檔名為 .proto,使用這種原始檔,可以定義儲存類的內容(訊息型別)。
protobuf有自己的編譯器 protoc,可以將 .proto 編譯成對應語言的檔案,就可以進行使用了。

工具安裝

brew install protobuf
get github.com/golang/protobuf/protoc-gen-go

檔案建立

// 指定的當前proto語法的版本,有2和3  
syntax = "proto3";  
//option go_package = "path;name"; path 表示生成的go檔案的存放地址,會自動生成目錄的  
// name 表示生成的go檔案所屬的包名  
option go_package="/service";  
// 指定等會檔案生成出來的package  
package service;  
  
message User {  
  string username = 1;  
  int32 age = 2;  
}

使用命令生成對應的GO檔案

 protoc --go_out=. user.proto

程式碼使用

package main  
  
import (  
    "clinet/service"  
    "fmt"    "google.golang.org/protobuf/proto")  
  
func main() {  
    user := &service.User{  
       Username: "mszlu",  
       Age:      20,  
    }  
    //轉換為protobuf  
    marshal, err := proto.Marshal(user) //將這個 User 物件編碼為 Protobuf 格式的位元組陣列 ([]byte)。  
    if err != nil {  
       panic(err)  
    }  
  
    newUser := &service.User{}  
    err = proto.Unmarshal(marshal, newUser)//將位元組陣列解碼回 User 物件  
    if err != nil {  
       panic(err)  
    }  
    fmt.Println(newUser.String())  
}

proto檔案介紹

message使用

一個訊息型別是透過關鍵字message欄位指定的。

message User {
  string username = 1;
  int32 age = 2;
}

欄位規則

  • required string name = 1;
    這個欄位是必填的,意味著在建立和序列化 Person 訊息時,必須設定 name 欄位的值。否則會報錯。在 Go 中,這個欄位會被生成為 string 型別。
  • optional int32 age = 2;
    這個欄位是可選的,意味著在建立和序列化 Person 訊息時,可以不設定 age 欄位。如果不設定,則會使用該欄位型別的預設值,在這裡是 0。在 Go 中,這個欄位會被生成為 int32 型別。
  • repeated string phoneNumbers = 3;
    這個欄位是可重複的,意味著一個 Person 訊息可以有多個電話號碼。在序列化和反序列化過程中,這些電話號碼的順序會被保留。在 Go 中,這個欄位會被生成為 []string 型別的切片。
message Person {
  required string name = 1;
  optional int32 age = 2;
  repeated string phoneNumbers = 3;
}
// 建立一個 Person 訊息
person := &Person{
    Name:         "Alice",
    Age:          30,
    PhoneNumbers: []string{"123-456-7890", "987-654-3210"},
}

// 序列化為 Protobuf 二進位制資料
data, err := proto.Marshal(person)
if err != nil {
    // 處理錯誤
}

// 反序列化回 Person 物件
newPerson := &Person{}
err = proto.Unmarshal(data, newPerson)
if err != nil {
    // 處理錯誤
}

// 輸出反序列化後的 Person 物件
fmt.Println(newPerson)

標識號

在訊息體的定義中,每個欄位都必須要有一個唯一的標識號,標識號是[0,2^29-1]範圍內的一個整數。

message Person { 
  string name = 1;  // (位置1)
  int32 id = 2;  
  optional string email = 3;  
  repeated string phones = 4; // (位置4)
}

訊息複雜使用

message PersonInfo {
    message Person {
        string name = 1;
        int32 height = 2;
        repeated int32 weight = 3;
    } 
	repeated Person info = 1;
}

如果你想在它的父訊息型別的外部重用這個訊息型別,你需要以PersonInfo.Person的形式使用它,如:

message PersonMessage {
	PersonInfo.Person info = 1;
}

定義服務

如果想要將訊息型別用在RPC系統中,可以在.proto檔案中定義一個RPC服務介面,protocol buffer 編譯器將會根據所選擇的不同語言生成服務介面程式碼及存根。

service SearchService {
	//rpc 服務的函式名 (傳入引數)返回(返回引數)
	rpc Search (SearchRequest) returns (SearchResponse);
}

上述代表表示,定義了一個RPC服務,該方法接收SearchRequest返回SearchResponse

service MyService {
  rpc GetUser(GetUserRequest) returns (GetUserResponse);
  rpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse);
}

message GetUserRequest {
  string userId = 1;
}

message GetUserResponse {
  User user = 1;
}

message UpdateUserRequest {
  User user = 1;
}

message UpdateUserResponse {
  bool success = 1;
}

message User {
  string name = 1;
  int32 age = 2;
}

MyService 是一個服務,它定義了兩個 RPC 方法:

  • GetUser: 輸入一個 GetUserRequest,返回一個 GetUserResponse。
  • UpdateUser: 輸入一個 UpdateUserRequest,返回一個 UpdateUserResponse。

參考文章

https://mszlu.com/grpc/01/01.html#_3-3-proto檔案介紹

相關文章