golang使用protobuf

簡介

http中常用的json協議一樣,protobuf也是用來傳輸數據的,但是它使用二進制格式,傳輸效率更高。

安裝

  1. 下載protoc二進制程序
    下載鏈接
    在windows上,選擇protoc-3.7.0-rc-2-win64.zip 進行下載。
    壓縮包中有兩個文件夾:
    壓縮包中文件夾.png

    bin目錄下的protoc.exe拷貝到GOPATH/bin目錄下,將include/目錄下的google文件夾拷貝GOPATH/src目錄下(只有使用protobuf的一些內置結構才需要用到該文件夾內的文件,這次并不會用到這個文件夾)。
  2. 安裝protobuf的編譯器插件protoc-gen-go
    protoc程序會調用protoc-gen-go,將.proto文件生成golang代碼。可以使用go get命令安裝:
go get -u -v github.com/golang/protobuf/protoc-gen-go

安裝成功后,會在GOPATH/bin下生成protoc-gen-go.exe程序。

例子

demo為官網tutorial的簡化版本。

在GOPATH/src/all-demo下,目錄結構為:

protobuf-demo
    demo1
        addressbook
            addressbook.pb.go
            addressbook.proto
        main.go

編寫、編譯addressbook.proto文件

其中addressbook.proto文件為:

// [START declaration]
syntax = "proto3";
package addressbook;

// [END declaration]

// [START messages]
message Person {
  string name = 1;
  int32 id = 2;  // Unique ID number for this person.
  string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
  }

  repeated PhoneNumber phones = 4;
}

// Our address book file is just one of these.
message AddressBook {
  repeated Person people = 1;
}
// [END messages]

其中:

  1. syntax設置語法類型,有proto2proto3兩種語法。
  2. package addressbook可以設置生成的golang代碼的包名。
  3. message對應于golang中的struct,可以看到文件中一共定義了PersonPhoneNumberAddressBook3個message,其中PhoneNumberPerson的嵌套類型。
  4. message中有字段,可以是intstring,枚舉或者其他消息類型。
  5. repeated表示該字段可以不止一個,類似于golang中的slice

編譯

命令行進入GOPATH/src/all-demo/protobuf-demo/demo1/addressbook,
執行protoc --go_out=. addressbook.proto,會在該目錄下生成addressbook.pb.go文件。
其中 --go_out指定生成的golang文件的目錄。

addressbook.pb.go部分源碼如下:

// 可以看到包名為addressbook
package addressbook
// [START messages]
type Person struct {
    Name                 string                `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
    Id                   int32                 `protobuf:"varint,2,opt,name=id,proto3" json:"id,omitempty"`
    Email                string                `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"`
    Phones               []*Person_PhoneNumber `protobuf:"bytes,4,rep,name=phones,proto3" json:"phones,omitempty"`
    XXX_NoUnkeyedLiteral struct{}              `json:"-"`
    XXX_unrecognized     []byte                `json:"-"`
    XXX_sizecache        int32                 `json:"-"`
}
type Person_PhoneNumber struct {
    Number               string           `protobuf:"bytes,1,opt,name=number,proto3" json:"number,omitempty"`
    Type                 Person_PhoneType `protobuf:"varint,2,opt,name=type,proto3,enum=addressbook.Person_PhoneType" json:"type,omitempty"`
    XXX_NoUnkeyedLiteral struct{}         `json:"-"`
    XXX_unrecognized     []byte           `json:"-"`
    XXX_sizecache        int32            `json:"-"`
}
// Our address book file is just one of these.
type AddressBook struct {
    People               []*Person `protobuf:"bytes,1,rep,name=people,proto3" json:"people,omitempty"`
    XXX_NoUnkeyedLiteral struct{}  `json:"-"`
    XXX_unrecognized     []byte    `json:"-"`
    XXX_sizecache        int32     `json:"-"`
}

編寫main.go

代碼主要測試proto.Marshalproto.UnMarshal的功能。先定義一個pb.AddressBook 結構體,并將其初始化,用proto.Marshal將其序列化成二進制數據,寫入文件,再將其從文件中讀取,使用proto.UnMarshal反序列化成結構體。
代碼如下:

package main

import (
    "fmt"
    pb "all-demo/protobuf-demo/demo1/addressbook"
    "io/ioutil"
    "log"
    "github.com/golang/protobuf/proto"
)

func main() {
    // 自定義AddressBook內容
    book := &pb.AddressBook{
        People: []*pb.Person {
            &pb.Person{
                Id: 1,
                Name: "zyq",
                Email: "77@qq.com",
                Phones: []*pb.Person_PhoneNumber{
                    &pb.Person_PhoneNumber {
                        Number: "11111",
                        Type: pb.Person_MOBILE,
                    },
                    &pb.Person_PhoneNumber {
                        Number: "22222",
                        Type: pb.Person_HOME,
                    },
                },
            },
        },
    }
    fmt.Println("book : ",book)

    fname := "address.dat"
    // 將book進行序列化
    out, err := proto.Marshal(book)
    if err != nil {
        log.Fatalln("Failed to encode address book:", err)
    }
    // 將序列化的內容寫入文件
    if err := ioutil.WriteFile(fname, out, 0644); err != nil {
        log.Fatalln("Failed to write address book:", err)
    }

    // 讀取寫入的二進制數據
    in, err := ioutil.ReadFile(fname)
    if err != nil {
        log.Fatalln("Error reading file:", err)
    }

    // 定義一個空的結構體
    book2 := &pb.AddressBook{}
    // 將從文件中讀取的二進制進行反序列化
    if err := proto.Unmarshal(in, book2); err != nil {
        log.Fatalln("Failed to parse address book:", err)
    }

    fmt.Println("book2: ",book2)
}

執行結果為:

book :  people:<name:"zyq" id:1 email:"77@qq.com" phones:<number:"11111" > phones:<number:"22222" type:HOME > > 
book2:  people:<name:"zyq" id:1 email:"77@qq.com" phones:<number:"11111" > phones:<number:"22222" type:HOME > > 

問題:

  • 還有很多語法不了解。
  • proto.UnMarshal函數的聲明為func Unmarshal(buf []byte, pb Message) error,該函數的第二個參數為proto.Message接口,在網絡傳輸中,服務端怎么知道客戶端發過來的到底是哪一種message

參考

  1. https://developers.google.com/protocol-buffers/docs/gotutorial
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容