golang使用protobuf

编程语言与开发

约 1268 字 预计阅读 3 分钟

文章目录

为什么要使用protobuf

最近的项目中,一直使用Json做数据传输。Json用起来的确很方便。但相对于protobuf数据量更大些。做一个移动端应用,为用户省点流量还是很有必要的。正好也可以学习一下protobuf的使用

跟Json相比protobuf性能更高,更加规范

  • 编解码速度快,数据体积小
  • 使用统一的规范,不用再担心大小写不同导致解析失败等蛋疼的问题了

但也失去了一些便利性

  • 改动协议字段,需要重新生成文件。
  • 数据没有可读性

安装

在go中使用protobuf,有两个可选用的包goprotobuf(go官方出品)和gogoprotobuf。
gogoprotobuf完全兼容google protobuf,它生成的代码质量和编解码性能均比goprotobuf高一些

安装protoc

首先去https://github.com/google/pro… 上下载protobuf的编译器protoc,windows上可以直接下到exe文件(linux则需要编译),最后将下载好的可执行文件拷贝到$GOPATH的bin目录下($GOPATH/bin目录最好添加到系统环境变量里)

安装protobuf库文件

go get github.com/golang/protobuf/proto

goprotobuf

安装插件

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

生成go文件

protoc —go_out=. *.proto

gogoprotobuf

安装插件

gogoprotobuf有两个插件可以使用

  • protoc-gen-gogo:和protoc-gen-go生成的文件差不多,性能也几乎一样(稍微快一点点)
  • protoc-gen-gofast:生成的文件更复杂,性能也更高(快5-7倍)
  1. //gogo
  2. go get github.com/gogo/protobuf/protoc-gen-gogo
  3. //gofast
  4. go get github.com/gogo/protobuf/protoc-gen-gofast

安装gogoprotobuf库文件

  1. go get github.com/gogo/protobuf/proto
  2. go get github.com/gogo/protobuf/gogoproto //这个不装也没关系

生成go文件

  1. //gogo
  2. protoc --gogo_out=. *.proto
  3. #要是没有找到protoc-gen-go,则要指定它: protoc --go_out=./ --plugin=/Users/easub/code/gopath/bin/protoc-gen-go test.proto
  4. //gofast
  5. protoc --gofast_out=. *.proto

性能测试

这里只是简单的用go test测试了一下

  1. //goprotobuf
  2. "编码"447ns/op
  3. "解码"422ns/op
  4. //gogoprotobuf-go
  5. "编码"433ns/op
  6. "解码"427ns/op
  7. //gogoprotobuf-fast
  8. "编码"112ns/op
  9. "解码"112ns/op

go_protobuf的简单使用

test.proto

  1. syntax = "proto3"; //指定版本,必须要写(proto3、proto2)
  2. package proto;
  3. enum FOO
  4. {
  5. X = 0;
  6. };
  7. //message是固定的。UserInfo是类名,可以随意指定,符合规范即可
  8. message UserInfo{
  9. string message = 1; //消息
  10. int32 length = 2; //消息大小
  11. int32 cnt = 3; //消息计数
  12. }

client_protobuf.go

  1. package main
  2. import (
  3. "bufio"
  4. "fmt"
  5. "net"
  6. "os"
  7. stProto "proto"
  8. "time"
  9. //protobuf编解码库,下面两个库是相互兼容的,可以使用其中任意一个
  10. "github.com/golang/protobuf/proto"
  11. //"github.com/gogo/protobuf/proto"
  12. )
  13. func main() {
  14. strIP := "localhost:6600"
  15. var conn net.Conn
  16. var err error
  17. //连接服务器
  18. for conn, err = net.Dial("tcp", strIP); err != nil; conn, err = net.Dial("tcp", strIP) {
  19. fmt.Println("connect", strIP, "fail")
  20. time.Sleep(time.Second)
  21. fmt.Println("reconnect...")
  22. }
  23. fmt.Println("connect", strIP, "success")
  24. defer conn.Close()
  25. //发送消息
  26. cnt := 0
  27. sender := bufio.NewScanner(os.Stdin)
  28. for sender.Scan() {
  29. cnt++
  30. stSend := &stProto.UserInfo{
  31. Message: sender.Text(),
  32. Length: *proto.Int(len(sender.Text())),
  33. Cnt: *proto.Int(cnt),
  34. }
  35. //protobuf编码
  36. pData, err := proto.Marshal(stSend)
  37. if err != nil {
  38. panic(err)
  39. }
  40. //发送
  41. conn.Write(pData)
  42. if sender.Text() == "stop" {
  43. returnpackage main
  44. import (
  45. "bufio"
  46. "fmt"
  47. "net"
  48. "os"
  49. stProto "study/proto"
  50. "time"
  51. //protobuf编解码库,下面两个库是相互兼容的,可以使用其中任意一个
  52. "github.com/golang/protobuf/proto"
  53. //"github.com/gogo/protobuf/proto"
  54. )
  55. func main() {
  56. strIP := "localhost:6600"
  57. var conn net.Conn
  58. var err error
  59. //连接服务器
  60. for conn, err = net.Dial("tcp", strIP); err != nil; conn, err = net.Dial("tcp", strIP) {
  61. fmt.Println("connect", strIP, "fail")
  62. time.Sleep(time.Second)
  63. fmt.Println("reconnect...")
  64. }
  65. fmt.Println("connect", strIP, "success")
  66. defer conn.Close()
  67. //发送消息
  68. cnt := 0
  69. sender := bufio.NewScanner(os.Stdin)
  70. for sender.Scan() {
  71. cnt++
  72. stSend := &stProto.UserInfo{
  73. Message: sender.Text(),
  74. Length: *proto.Int(len(sender.Text())),
  75. Cnt: *proto.Int(cnt),
  76. }
  77. //protobuf编码
  78. pData, err := proto.Marshal(stSend)
  79. if err != nil {
  80. panic(err)
  81. }
  82. //发送
  83. conn.Write(pData)
  84. if sender.Text() == "stop" {
  85. return
  86. }
  87. }
  88. }

server_protobuf.go

  1. package main
  2. import (
  3. "fmt"
  4. "net"
  5. "os"
  6. stProto "study/proto"
  7. //protobuf编解码库,下面两个库是相互兼容的,可以使用其中任意一个
  8. "github.com/golang/protobuf/proto"
  9. //"github.com/gogo/protobuf/proto"
  10. )
  11. func main() {
  12. //监听
  13. listener, err := net.Listen("tcp", "localhost:6600")
  14. if err != nil {
  15. panic(err)
  16. }
  17. for {
  18. conn, err := listener.Accept()
  19. if err != nil {
  20. panic(err)
  21. }
  22. fmt.Println("new connect", conn.RemoteAddr())
  23. go readMessage(conn)
  24. }
  25. }
  26. //接收消息
  27. func readMessage(conn net.Conn) {
  28. defer conn.Close()
  29. buf := make([]byte, 4096, 4096)
  30. for {
  31. //读消息
  32. cnt, err := conn.Read(buf)
  33. if err != nil {
  34. panic(err)
  35. }
  36. stReceive := &stProto.UserInfo{}
  37. pData := buf[:cnt]
  38. //protobuf解码
  39. err = proto.Unmarshal(pData, stReceive)
  40. if err != nil {
  41. panic(err)
  42. }
  43. fmt.Println("receive", conn.RemoteAddr(), stReceive)
  44. if stReceive.Message == "stop" {
  45. os.Exit(1)
  46. }
  47. }
  48. }

生成后的myproto.go文件如下,包名还可以改,哈哈。:

  1. // Code generated by protoc-gen-go. DO NOT EDIT.
  2. // source: test.proto
  3. package proto //包名是可以改的
  4. import (
  5. fmt "fmt"
  6. proto "github.com/golang/protobuf/proto"
  7. math "math"
  8. )
  9. // Reference imports to suppress errors if they are not otherwise used.
  10. var _ = proto.Marshal
  11. var _ = fmt.Errorf
  12. var _ = math.Inf
  13. // This is a compile-time assertion to ensure that this generated file
  14. // is compatible with the proto package it is being compiled against.
  15. // A compilation error at this line likely means your copy of the
  16. // proto package needs to be updated.
  17. const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
  18. type FOO int32
  19. const (
  20. FOO_X FOO = 0
  21. )
  22. var FOO_name = map[int32]string{
  23. 0: "X",
  24. }
  25. var FOO_value = map[string]int32{
  26. "X": 0,
  27. }
  28. func (x FOO) String() string {
  29. return proto.EnumName(FOO_name, int32(x))
  30. }
  31. func (FOO) EnumDescriptor() ([]byte, []int) {
  32. return fileDescriptor_c161fcfdc0c3ff1e, []int{0}
  33. }
  34. //message是固定的。UserInfo是类名,可以随意指定,符合规范即可
  35. type UserInfo struct {
  36. Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
  37. Length int32 `protobuf:"varint,2,opt,name=length,proto3" json:"length,omitempty"`
  38. Cnt int32 `protobuf:"varint,3,opt,name=cnt,proto3" json:"cnt,omitempty"`
  39. XXX_NoUnkeyedLiteral struct{} `json:"-"`
  40. XXX_unrecognized []byte `json:"-"`
  41. XXX_sizecache int32 `json:"-"`
  42. }
  43. func (m *UserInfo) Reset() { *m = UserInfo{} }
  44. func (m *UserInfo) String() string { return proto.CompactTextString(m) }
  45. func (*UserInfo) ProtoMessage() {}
  46. func (*UserInfo) Descriptor() ([]byte, []int) {
  47. return fileDescriptor_c161fcfdc0c3ff1e, []int{0}
  48. }
  49. func (m *UserInfo) XXX_Unmarshal(b []byte) error {
  50. return xxx_messageInfo_UserInfo.Unmarshal(m, b)
  51. }
  52. func (m *UserInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
  53. return xxx_messageInfo_UserInfo.Marshal(b, m, deterministic)
  54. }
  55. func (m *UserInfo) XXX_Merge(src proto.Message) {
  56. xxx_messageInfo_UserInfo.Merge(m, src)
  57. }
  58. func (m *UserInfo) XXX_Size() int {
  59. return xxx_messageInfo_UserInfo.Size(m)
  60. }
  61. func (m *UserInfo) XXX_DiscardUnknown() {
  62. xxx_messageInfo_UserInfo.DiscardUnknown(m)
  63. }
  64. var xxx_messageInfo_UserInfo proto.InternalMessageInfo
  65. func (m *UserInfo) GetMessage() string {
  66. if m != nil {
  67. return m.Message
  68. }
  69. return ""
  70. }
  71. func (m *UserInfo) GetLength() int32 {
  72. if m != nil {
  73. return m.Length
  74. }
  75. return 0
  76. }
  77. func (m *UserInfo) GetCnt() int32 {
  78. if m != nil {
  79. return m.Cnt
  80. }
  81. return 0
  82. }
  83. func init() {
  84. proto.RegisterEnum("proto.FOO", FOO_name, FOO_value)
  85. proto.RegisterType((*UserInfo)(nil), "proto.UserInfo")
  86. }
  87. func init() { proto.RegisterFile("test.proto", fileDescriptor_c161fcfdc0c3ff1e) }
  88. var fileDescriptor_c161fcfdc0c3ff1e = []byte{
  89. // 122 bytes of a gzipped FileDescriptorProto
  90. 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2a, 0x49, 0x2d, 0x2e,
  91. 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x53, 0x4a, 0x7e, 0x5c, 0x1c, 0xa1, 0xc5,
  92. 0xa9, 0x45, 0x9e, 0x79, 0x69, 0xf9, 0x42, 0x12, 0x5c, 0xec, 0xb9, 0xa9, 0xc5, 0xc5, 0x89, 0xe9,
  93. 0xa9, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x30, 0xae, 0x90, 0x18, 0x17, 0x5b, 0x4e, 0x6a,
  94. 0x5e, 0x7a, 0x49, 0x86, 0x04, 0x93, 0x02, 0xa3, 0x06, 0x6b, 0x10, 0x94, 0x27, 0x24, 0xc0, 0xc5,
  95. 0x9c, 0x9c, 0x57, 0x22, 0xc1, 0x0c, 0x16, 0x04, 0x31, 0xb5, 0x78, 0xb8, 0x98, 0xdd, 0xfc, 0xfd,
  96. 0x85, 0x58, 0xb9, 0x18, 0x23, 0x04, 0x18, 0x92, 0xd8, 0xc0, 0x96, 0x18, 0x03, 0x02, 0x00, 0x00,
  97. 0xff, 0xff, 0x27, 0x2c, 0x8f, 0x3c, 0x79, 0x00, 0x00, 0x00,
  98. }

测试下:

  1. go run go run server_protobuf.go
  2. go run client_protobuf.go

然后在client终端输入你想输入的文字,然后看下服务端,你会发现,服务端收到了.哈.
我的文件结构如下:
golang目录结构

来源文章作者 虞双齐
https://yushuangqi.com/blog/2017/golangshi-yong-protobuf.html#%E7%94%9F%E6%88%90go%E6%96%87%E4%BB%B6

分类: web

标签:   golang