>

简介

Protobuf 源码中默认实现了对 C++, Java, C#, Python 等语言的生成器插件,但是没有 Go 语言的生成器插件。Go 语言的生成器插件是在另一个叫 golang/protobuf 的项目中提供的,也叫 goprotobuf,这篇文章主要就是介绍如何在 Go 语言中使用 Protobuf。

安装 golang/protobuf

1
go get -u github.com/golang/protobuf/protoc-gen-go

执行上述命令之后,protoc-gen-go 二进制程序默认会安装在 $GOBIN 目录中。

注:
golang/protobuf 中包含了 github.com/golang/protobuf/proto 库,这个库定义了 .proto 文件中声明的类型和方法所对应的 Go 语言表示的类型和方法,也就是由 protoc-gen-go 所生成的 Go 源码文件中所有需要引用到的类型和方法。

比如 proto/lib.go 中包含大量常用的类型及方法:

1
2
3
4
5
6
7
proto.String()
proto.Int64()
proto.Uint64()
proto.Bool()
proto.Marshal()
proto.Unmarshal()
proto.Message

编译生成 Go 源码

执行 protoc,并使用 --go_out 选项指定输出目录,即可生成 Go 源码文件。因为安装了 protoc-gen-go 之后,--go_out 选项会自动搜索 protoc-gen-go,只要其在 PATH 目录中可以找到即可。

--go_out 支持以下参数

  • plugins=plugin1+plugin2 指定插件,目前只支持 grpc,即:plugins=grpc
  • M 参数 指定导入的.proto文件路径编译后对应的golang包名(不指定本参数默认就是.proto文件中import语句的路径)
  • import_prefix=xxx 为所有 import 路径添加前缀,主要用于编译子目录内的多个 proto 文件,这个参数按理说很有用,尤其适用替代一些情况时的 M 参数。
  • import_path=foo/bar 用于指定未声明 package 或 go_package 的文件的包名,最右面的斜线前的字符会被忽略

例子

1) 编写一个 msg.proto IDL 文件,如下:

1
2
3
4
5
6
7
8
syntax="proto3";
package example;

message Msg {
int32 msgType = 1;
string MsgInfo = 2;
string MsgFrom = 3;
}
注意:
  • 在最新版的 Protobuf 中,强制要求 proto 文件必须指定版本,否则编译报错
  • 这里定义的所有字段(无论大小写),在生成的 Go 语言中的结构体中都是导出字段,也就是都会变成大写。

2) 编译 .proto 文件,生成 Go 语言文件

执行 protoc --go_out=. msg.proto 生成对应的 msg.pb.go 文件
进入 msg.pb.go 所在目录,执行 go install 生成相应的包。

3) 在 Go 程序中使用 protobuf

创建 main.go,并输入如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import (
"github.com/google/protobuf/proto"
"example"
"fmt"
"os"
)

func main() {
msg_test := &example.Msg{
MsgType: proto.Int32(1),
MsgInfo: proto.String("I am hahaya."),
MsgFrom: proto.String("127.0.0.1"),
}

in_data, err := proto.Marshal(msg_test)
if err != nil {
fmt.Println("Marshaling error: ", err)
os.Exit(1)
}

msg_encoding := &example.Msg{}
err = proto.Unmarshal(in_data, msg_encoding)
if err != nil {
fmt.Println("Unmarshaling error: ", err)
os.Exit(1)
}
fmt.Printf("msg type: %d\n", msg_encoding.GetMsgType())
fmt.Printf("msg info: %s\n", msg_encoding.MsgInfo)
fmt.Printf("msg from: %s\n", msg_encoding.MsgFrom)
}

知识扩展

除了 Google 官方提供的这款 golang/protobuf 插件,还有一款非常优秀的开源第三方库 gogo/protobuf,其作为官方的 fork,做了非常多的性能上的优化,能提供更加快速的编解码实现。

gogo/protobuf 的源码地址:https://github.com/gogo/protobuf

根据其官方介绍,它提供了许多不同优化级别的实现,比如:

1
2
3
4
5
6
proto-gen-gofast
proto-gen-gogo
proto-gen-gogofast
proto-gen-gogofaster
proto-gen-gogoslick
...
gogo/protobuf 的安装和使用

安装

1
go get -u github.com/gogo/protobuf/protoc-gen-gofast

使用

1
protoc --gofast_out=. test.proto