Go 語言,Protobuf 極速入門!

Protobuf 是 Protocol Buffers 的簡稱,是一種與語言、平臺無關,可擴展的序列化結構化數據的數據描述語言,Protobuf 作爲接口規範的描述語言,可以作爲設計安全的跨語言 PRC 接口的基礎工具。

基本語法

hello.proto 文件

syntax = "proto3";

package main;

message String {
    string value = 1;
}
  1. 第一行聲明使用 proto3 語法。否則,默認使用 proto2 語法,目前主流推薦使用 v3 版本。此聲明必須是文件的非空、非註釋的第一行。

  2. package 指令指明當前是 main 包,用戶也可以針對不同的語言定製對應的包路徑和名稱。

  3. message 關鍵字定義一個 String 類型消息體,在最終生成的 Go 語言代碼中對應一個 String 結構體。每一個消息體的字段包含三個屬性:類型、字段名稱、字段編號。在消息體的定義上,除類型以外均不可重複。此處 String 類型中只有一個字符串類型的 value 成員,該成員編碼時用 1 編號代替名字。

  4. Protobuf 中最基本的數據單元是 message,類似 Go 語言中的結構體。在 message 中可以嵌套 message 或其它的基礎數據類型的成員。

關於標識號

消息體中字段定義了唯一的數字值。這些數字是用來在消息的二進制格式中識別各個字段的,一旦開始使用就不能夠再改變。注:[1,15] 之內的標識號在編碼的時候會佔用一個字節。[16,2047] 之內的標識號則佔用 2 個字節。所以應該爲那些頻繁出現的消息元素保留 [1,15] 之內的標識號。

最小的標識號可以從 1 開始,最大到 2^29 - 1, or 536,870,911。不可以使用其中的 [19000-19999] 的標識號, Protobuf 協議實現中對這些進行了預留。如果非要在 .proto 文件中使用這些預留標識號,編譯時就會報警。類似地,你不能使用之前保留的任何標識符。

添加註釋

.proto 文件添加註釋,可以使用 C/C++ 風格的 // 和 /* … */ 語法格式

保留字段

如果從前面定義的消息中刪除了 和 字段,應保留其字段編號,使用關鍵字 reserved:

syntax "proto3";

message Stock {
    reserved 3, 4;
    // ...
}

還可以將 reserved 關鍵字用作將來可能添加的字段的佔位符。可以使用 to 關鍵字將連續字段號佔位。

syntax "proto3";

message Info {
    reserved 2, 9 to 11, 15;
    // ...
}

生成相應的 Go 代碼

Protobuf 核心的工具集是 C++ 語言開發的,官方的 protoc 編譯器中並不支持 Go 語言。要想基於上面 的 hello.proto 文件生成相應的 Go 代碼,需要安裝相應的插件。

通過以下命令生成相應的 Go 代碼:

$ protoc --go_out=. hello.proto

基本數據類型

protobuf 所生成出來的數據類型並非與原始的類型完全一致,下面是一些常見的類型映射:

生成的 hello.pb.go 文件

pb.go 文件是對 proto 文件所生成的對應的 Go 代碼,在實際應用中將會引用到此文件。

// Code generated by protoc-gen-go. DO NOT EDIT.
// source: hello.proto

package main

import (
 fmt "fmt"
 proto "github.com/golang/protobuf/proto"
 math "math"
)

// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf

// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package

type String struct {
 Value                *String  `protobuf:"bytes,1,opt,`
 XXX_NoUnkeyedLiteral struct{} `json:"-"`
 XXX_unrecognized     []byte   `json:"-"`
 XXX_sizecache        int32    `json:"-"`
}

func (m *String) Reset()         { *m = String{} }
func (m *String) String() string { return proto.CompactTextString(m) }
func (*String) ProtoMessage()    {}
func (*String) Descriptor() ([]byte, []int) {
 return fileDescriptor_61ef911816e0a8ce, []int{0}
}

func (m *String) XXX_Unmarshal(b []byte) error {
 return xxx_messageInfo_String.Unmarshal(m, b)
}
func (m *String) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
 return xxx_messageInfo_String.Marshal(b, m, deterministic)
}
func (m *String) XXX_Merge(src proto.Message) {
 xxx_messageInfo_String.Merge(m, src)
}
func (m *String) XXX_Size() int {
 return xxx_messageInfo_String.Size(m)
}
func (m *String) XXX_DiscardUnknown() {
 xxx_messageInfo_String.DiscardUnknown(m)
}

var xxx_messageInfo_String proto.InternalMessageInfo

func (m *String) GetValue() *String {
 if m != nil {
  return m.Value
 }
 return nil
}

func init() {
 proto.RegisterType((*String)(nil), "main.String")
}

func init() { proto.RegisterFile("hello.proto", fileDescriptor_61ef911816e0a8ce) }

var fileDescriptor_61ef911816e0a8ce = []byte{
 // 84 bytes of a gzipped FileDescriptorProto
 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xce, 0x48, 0xcd, 0xc9,
 0xc9, 0xd7, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0xc9, 0x4d, 0xcc, 0xcc, 0x53, 0xd2, 0xe1,
 0x62, 0x0b, 0x2e, 0x29, 0xca, 0xcc, 0x4b, 0x17, 0x52, 0xe2, 0x62, 0x2d, 0x4b, 0xcc, 0x29, 0x4d,
 0x95, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x36, 0xe2, 0xd1, 0x03, 0xc9, 0xeb, 0x41, 0x24, 0x83, 0x20,
 0x52, 0x49, 0x6c, 0x60, 0xad, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x76, 0x0c, 0x0e, 0x54,
 0x49, 0x00, 0x00, 0x00,
}

Protobuf 和 RPC 組合

基於 String 類型,重新實現 HelloService 服務

package main

import (
 "log"
 "net"
 "net/rpc"
 "rpc/protoc"
)

// HelloService is rpc server obj
type HelloService struct{}

//Hello方法的輸入參數和輸出的參數均改用 Protobuf 定義的 String 類型表示。
//因爲新的輸入參數爲結構體類型,因此改用指針類型作爲輸入參數,函數的內部代碼同時也做了相應的調整。
func (p *HelloService) Hello(request *protoc.String, reply *protoc.String) error {
 reply.Value = "hello:" + request.GetValue()
 return nil
}

func main() {
 rpc.RegisterName("HelloService", new(HelloService))

 listener, err := net.Listen("tcp"":1234")
 if err != nil {
  log.Fatal("ListenTCP error:", err)
 }

 conn, err := listener.Accept()
 if err != nil {
  log.Fatal("Accept error", err)
 }

 rpc.ServeConn(conn)
}

下面是客戶端請求 HelloService 服務的代碼 client.go:

package main

import (
 "fmt"
 "log"
 "net/rpc"
 "rpc/protoc"
)

func main() {
 client, err := rpc.Dial("tcp""localhost:1234")
 if err != nil {
  log.Fatal("dialing err:", err)
 }

 var reply = &protoc.String{}
 var param = &protoc.String{
  Value: "hello wekenw",
 }

 err = client.Call("HelloService.Hello", ¶m, &reply)
 if err != nil {
  log.Fatal(err)
 }
 fmt.Println(reply)
}

開啓服務器端,開啓客戶端。客戶端的執行結果如下:

$ go run client.go
value:"hello:hello wekenw"

參考資料:

https://blog.csdn.net/qq_22660775/article/details/89163881

https://docs.microsoft.com/zh-cn/dotnet/architecture/grpc-for-wcf-developers/protobuf-reserved

https://golang2.eddycjy.com/posts/ch3/01-simple-grpc-protobuf/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/feSGPUL8pB5lhvYfxFdHlQ