Protocol buffers are the flexible, efficient, automated solution to solve the problem of serializing and retrieving the structured data. With protocol buffers, you write a .proto
description of the data structure you wish to store. From that, the protocol buffer compiler creates a class that implements automatic encoding and parsing of the protocol buffer data with an efficient binary format. Importantly, the protocol buffer format supports the idea of extending the format over time in such a way that the code can still read data encoded with the old format.
How to define the protocol format?
First, to create your address book application, you’ll need to start with a
.proto
file.
The .proto
file starts with a package declaration, just like this:
syntax = "proto3";
package tutorial;
import "goole/protobuf/timestamp.protp"
we can see that, the format is just like a .go
file! It just like Go, the package name is used as the Go package, unless you have specified a go_package
。
Next, you have your message definitions. Many standard simple data types are available: bool
,int32
,float
,double
,string
。
For example:
message Person {
string name = 1;
int32 id = 2;
string email = 3;
enum PhotoType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhonoNumber {
string number = 1;
PhotoType type = 2;
}
repeated PhoneNumber phones = 4;
google.protobuf.Timestamp last_updated = 5;
}
// Our address book file is just one of these.
message AddressBook {
repeated Person people = 1;
}
In the above example, the Person
message contains PhoneNumber
messages, while the AddressBook
message contains Person
messages. Also, you can use enum
if you want one of your fields to have one of a predefined list of values.
The " = 1", " = 2" markers on each element identify the unique “tag” that field uses in the binary encoding. Normally, we use 1-15 for one less byte to encode. Each element in a repeated field requires re-encoding the tag number, so repeated fields are particularly good candidates for this
optimization.
If a field value isn’t set, a default value is used: zero for numeric types, the empty string for strings, false for bools.
If a field is repeated
, the field may be repeated any
number of times (including zero). The order of the repeated values will
be preserved in the protocol buffer. Think of repeated fields as
dynamically sized arrays.
How to user the .proto file?
Now we have a .proto
file. But how to use that?
1.we need to install the complier, we can get them from the github
2.run the command
go get -u github.com/golang/protobuf/protoc-gen-go
3.run the compiler, specifying the source directory, the destination directory, and the path to our .proto
.we can use the command like this:
protoc -I=$SRC_DIR --go_out=$DST_DIR $SRC_DIR/addressbook.proto
Because you want Go classes, you use the --go_out
option – similar options are provided for other supported languages.
Then, a file named like .pb.go
will be created in our destination directory.
The protocol buffer API
From the file we have made. We can get many useful types:
- An
AddressBook
structure with aPeople
field. - A
Person
structure with fields forName
,Id
,Email
andPhones
. - A
Person_PhoneNumber
structure, with fields forNumber
andType
. - The type
Person_PhoneType
and a value defined for each value in thePerson.PhoneType
enum.
Here is a example of how you might create an instance of Person::
p := pb.Person {
Id: 1234,
Name: "John Doe",
Email: "[email protected]",
Phones: []*pb.Person_PhoneNumber {
{Number: "555-4321", Type: pb.Person_HOME},
},
}
Writing a Message
The whole purpose of using protocol buffers is to serialize your data so
that it can be parsed elsewhere.
In Go, you use the proto
library’s Marshal function to serialize your protocol buffer data. A pointer to a protocol buffer message’s struct
implements the proto.Message
interface. Calling proto.Marshal
returns the protocol buffer, encoded in its wire format.
For example:
book := &pb.AddressBook{}
// ...
// Write the new address book back to disk
out, err := proto.Marshal(bool)
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)
}
Reading a Message
To parse an encoded message, you use the proto
library’s Unmarshal function.
For example:
// Read the existing address book.
in, err := ioutil.ReadFile(fname)
if err != nil {
log.Fatalln("Error reading file:", err)
}
book := &pb.AddressBook{}
if err := proto.Unmarshal(in, book); err != nil {
log.Fatalln("Failed to parse address book:", err)
}
Extending a protocol buffer
When we write a new .proto
from an old .proto
, there are some rules we need to follow:
- must not change the tag numbers of any existing fields.
- may delete fields.
- may add new fields but you must use fresh tag numbers.(i.e. tag numbers that were never used in this protocol buffer, not even by deleted fields.)
Reference: https://developers.google.cn/protocol-buffers/docs/gotutorial