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

推荐阅读更多精彩内容