golang 使用 protobuf

jinchunguang · · 4805 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

Protobuf3 语法指南: https://colobu.com/2017/03/16/Protobuf3-language-guide/ 原文:https://www.jianshu.com/p/29db52241669 # 1 Protobuf简介 Google Protocol Buffer( 简称 Protobuf)是Google公司内部的混合语言数据标准,他们主要用于RPC系统和持续数据存储系统。 # 2 Protobuf 应用场景 Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或RPC数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。 简单来说,Protobuf的功能类似于XML,即负责把某种数据结构的信息,以某种格式保存起来。主要用于数据存储、传输协议等使用场景。 # 3 Protobuf 优缺点 ### 优点 * **性能好/效率高** * 时间维度:XML,JSON格式对数据进行序列化和反序列化时性能差。 * 空间维度:XML,JSON格式为了可读性,必然在进行存储数据时,也会消耗空间。 整体而言,Protobuf以高效的二进制方式存储,比XML小3到10倍,快20到100倍。 * **代码生成机制** * 代码生成机制能够极大解放开发者编写数据协议解析过程的时间,提高工作效率;于开发者维护和迭代。 * **支持“向后兼容”和“向前兼容”** * **支持多种编程语言** * Protobuf不仅仅Google开源的一个数据协议,还有很多种语言的开源项目实现。 ### 缺点 * **可读性较差** 为了提高性能,Protobuf采用了二进制格式进行编码。二进制格式编码对于开发者来说,是没办法阅读的。在进行程序调试时,比较困难。对于可读性要求高的数据报文程序就不建议使用。 * **缺乏自描述** Protobuf是通过二进制格式进行数据传输,开发者面对二进制格式的Protobuf,没有办法知道所对应的真实的数据结构,因此在使用Protobuf协议传输时,必须配备对应的proto配置文件。 # 4 Go语言中的编程实现 ## 4.1 安装protobuf编译器 使用release版本下载地址: https://github.com/protocolbuffers/protobuf/releases ### 方式一: 下载对应平台的二进制文件,配置环境变量即可 ![image.png](https://upload-images.jianshu.io/upload_images/1779921-ec1bf411b3351d45.png) ### 方式二 由于我本机下载二进制文件太慢,这里使用编译安装 Ubuntu 安装`(如果是centos直接将apt-get改为yum,如果是alpine将apt-get install 改为apk add)` ```shell #下载 sudo apt-get install autoconf automake libtool curl make g++ unzip git clone https://github.com/protocolbuffers/protobuf.git cd protobuf git submodule update --init --recursive ./autogen.sh #编译 ./configure make make check #安装 sudo make install sudo ldconfig # 检查 protoc -h ``` ## 4.2 安装go插件 protoc-gen-go * proto:实现了对编码,解码等运行时支持。这个在程序中会用到. * protoc-gen-go: 编译器插件:生成Go源文件。 ``` # 下载 go get -u -v github.com/golang/protobuf/protoc-gen-go # 安装 cd $GOPATH/src/github.com/golang/protobuf/protoc-gen-go/ go install ``` 此时会在`$GOPATH/bin/`生成二进制文件 ``` ls $GOPATH/bin/protoc-gen-go /home/jinchunguang/go/bin/protoc-gen-go ``` # 5 使用Protobuf的步骤 ## 5.1 编写proto文件 ``` # 创建项目目录 mkdir -pv protobuf-app/pb cd protobuf-app # 使用go mod go mod init protobuf-app # 编写协议文件 touch pb/person.proto vim pb/person.proto ``` **person.proto文件内容** ``` syntax = "proto3"; package pb; message Person { string Name = 1; int32 Age = 2; string From = 3; } ``` ## 5.2 生成协议文件 ``` protoc --proto_path=./pb --go_out=./pb ./pb/person.proto ``` 此时就会发现多了一个`person.pb.go`,这个就是生成的的协议文件 ``` tree . ├── go.mod └── pb ├── person.pb.go └── person.proto 1 directory, 3 files ``` ## 5.3 测试 下载编码解码库,也可以不下载,运行时`go mod`会自动解决依赖. ``` go get -u -v github.com/golang/protobuf/proto ``` **main.go** ``` package main import ( "fmt" // 编解码库 "github.com/golang/protobuf/proto" "protobuf-app/pb" ) func main() { p := &pb.Person{ Name: "Jack", Age: 10, From: "China", } fmt.Println("原始数据:",p) // 序列化 dataMarshal, err := proto.Marshal(p) if err != nil { fmt.Println("proto.Unmarshal.Err: ", err) return } fmt.Println("编码数据:",dataMarshal) // 反序列化 entity := pb.Person{} err = proto.Unmarshal(dataMarshal, &entity) if err != nil { fmt.Println("proto.Unmarshal.Err: ", err) return } fmt.Printf("解码数据: 姓名:%s 年龄:%d 国籍:%s ", entity.GetName(),entity.GetAge(),entity.GetFrom()) } ``` 执行程序 ![image.png](https://upload-images.jianshu.io/upload_images/1779921-da3acf2c38c61498.png) # 6 使用protobuf实现简单的Tcp通讯 ## 6.1 新建proto文件 `message.proto` 文件内容 ``` syntax = "proto3"; package pb; message Message{ string message = 1; int32 length = 2; } ``` 生成协议文件 ``` protoc --proto_path=./pb --go_out=./pb ./pb/message.proto ``` ## 6.2 新建立srv目录,添加 client,server 目录并且新建对应的go程序 ![image.png](https://upload-images.jianshu.io/upload_images/1779921-9bc49e327a85c494.png) **client.go** ```go package main import ( "fmt" "github.com/golang/protobuf/proto" "protobuf-app/pb" "net" ) func main() { var err error var msg string // 收集消息 fmt.Println("请输入短消息:") if _, err := fmt.Scanf("%s", &msg); err != nil { fmt.Printf("%s\n", err) return } // 编码数据 user := &pb.Message{ Message: msg, Length: *proto.Int(len(msg)), } pbData, err := proto.Marshal(user) if err != nil { fmt.Println("proto marshal err ", err) return } // 连接server address := "localhost:6600" conn, err := net.Dial("tcp", address); defer conn.Close() if err != nil { fmt.Println("net dial err ", err) return } // 发送数据 n, err := conn.Write(pbData) if err != nil { fmt.Println("net write err ", err) return } fmt.Println("conn write len:", n) } ``` **server.go** ```go package main import ( "fmt" "github.com/golang/protobuf/proto" "protobuf-app/pb" "io" "net" ) func main() { address := "localhost:6600" listener, err := net.Listen("tcp", address) if err != nil { fmt.Errorf("listen err:", err) } fmt.Println("[START] Server listenner: ", address) for { conn, err := listener.Accept() if err != nil { fmt.Println("conn err:", err) return } // 异步执行请求业务 go processing(conn) } } func processing(conn net.Conn) { // 延迟关闭 defer conn.Close() // 缓冲 buf := make([]byte, 4096) for { len, err := conn.Read(buf) // 读取结束 if err == io.EOF { return } if err != nil { fmt.Println("conn read err:", err) return } user := &pb.Message{} err = proto.Unmarshal(buf[:len], user) if err != nil { fmt.Println("proto unmarshal err:", err) return } fmt.Println("receive data:%v ip:%v ", user.Message, conn.RemoteAddr()) } } ``` ## 6.3 测试 启动服务: ``` go run server.go ``` ![客户端](https://upload-images.jianshu.io/upload_images/1779921-5253c947828f971e.png) ![服务端](https://upload-images.jianshu.io/upload_images/1779921-c19c10eb7190d159.png)

有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

4805 次点击  
加入收藏 微博
1 回复  |  直到 2020-07-31 15:07:22
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传