• protocol-buffer安装和使用



    protocol buffer是Google发布的一种独立的数据交换格式,类似于json,用于数据的序列化和解析。不同点是不能直接在各编程语言中使用,需要先在一个proto文件中定义需要传输的数据格式,然后使用proto工具把proto文件编译成想要的语言,如java、go、php等。然后在代码中,使用语言对应的protocol buffer包调用proto工具生成的文件,完成数据的序列化

    安装protoc编译工具

    首先安装protoc编译工具,在http://github.com/google/protobuf/releases,根据自己的系统选择包,下载后解压。我是win10 64,所以选择protoc-21.4-win64.zip,解压后放在了E盘。

    添加环境变量,让系统命令可以识别protoc命令。
    配置环境变量
    PB_PATH=E:\protoc-3.21.4-win64
    PATH=%PB_PATH%\bin

    添加后,在命令行执行 protoc,返回信息,表示安装成功

    编写proto文件

    创建一个user.proto文件,该文件下可以定义一些user相关的需要加密的字段

    //引入其他proto文件,就可以使用该文件中定义的message类型,定义本文件中的message下的字段
    import "myproject/other_protos.proto";
    
    // 指定的当前proto语法的版本
    syntax = "proto3";
    
    // 指定生成出来的文件的package,用来避免同一个文件调同名的消息时冲突
    // 包说明符对生成代码的影响取决于您选择的语言,php会生成命名空间,go会生成package
    package service;
    
    //根据编程语言不同,参数名不同
    //下面代码指定了生成go文件的生成目录,文件的包名。不设置包名时,包名默认为go文件所在目录名。会覆盖package设置的go的包名
    //路径以执行命令的目录为当前目录,寻找相对路径
    option go_package="./;service";
    
    //定义消息类型,通过关键字message字段指定的,消息就是需要传输的数据格式的定义
    message User {
    // required必传;optional 可选;repeated 可重复初入。不写前缀默认为required
    // 字段名后的数字不是默认值,是标识。标识号是[0,2^29-1]范围内的一个整数。[1-15]内的标识号在编码时只占用一个字节,包含标识符和字段类型,[16-2047]之间的标识符占用2个字节。建议为频繁出现的字段使用[1-15]间的标识符。
    //字段类型除了除了常规类型,也可使用import引入文件中定义的message类型
    	required string sername = 1;
    	optional int32 age = 2;
    	repeated int32 height = 3;
    }
    
    //定义服务类型,用作rpc通讯
    service SearchService {
        //rpc 服务的函数名 (传入参数)返回(返回参数)
        //传入和返回参数,需要使用proto中定义的message
        rpc Search (SearchRequest) returns (SearchResponse);
    }
    
    • 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

    生成指定语言的proto文件

    protoc内置9中语言的编译插件,如下
    在这里插入图片描述
    我们以go为例。protoc没有内置go的编译器,需要引入外部插件protoc-gen-go

    我们使用命令

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

    下载并编译了包,包中有main.go文件,所以在GOPATH/bin目录下生成了可执行文件protoc-gen-go.exe,这个文件就是我们在protoc中用到的插件

    我们也可以自定义自己的插件。插件名必须以protoc-gen-插件名.exe命名
    在调用时以:插件_out=参数 调用,之后回讲到如何制作插件

    我们以go_out插件为例,执行命令

    命令行后的第一个参数为输出目录。但是我们已经在go_package中指定了目录,所以第一个参数是无效的,填当前目录.即可。第二个参数是,要编译的文件路径和文件名(以命令执行目录为当前目录的相对路径)
    protoc后可以追加1个或多个 -I=path 指定解析import指令时要在其中查找.proto文件的目录,若文件没有使用import,则不需要该参数

    protoc --go_out=.  proto_type/hello.proto
    
    • 1

    报错
    在这里插入图片描述
    protoc默认会从环境变量path下的路径中寻找插件,没找到报错。
    查看发现path中只配置了go的安装GOROOT目录下的bin,而protoc-gen-go.exe在GOPATH的bin目录下

    我们把protoc-gen-go.exe复制到GOROOT的bin目录下,即可。或者在环境变量path中添加gopath/bin目录。再次执行命令即可

    在这里插入图片描述

    调用proto

    import (
    	"fmt"
    	"github.com/golang/protobuf/proto"
    )
    func main() {
    	//定义一个要加密的变量
    	msg := &protoc_type.User{
    		Username:"zhangsan",
    		Age:12,
    	}
    	//加密
    	marshal,err := proto.Marshal(msg)
    	if err!=nil{
    		fmt.Println(err.Error())
    	}else{
    		fmt.Println(marshal)
    	}
    	//定义一个变量,用于接收解码的值,类型必须用加密的值类型一样
    	unmarshal := &protoc_type.User{}
    	//解码
    	err2 := proto.Unmarshal(marshal,unmarshal)
    	if err2 == nil{
    		fmt.Println(unmarshal)
    		fmt.Println(unmarshal.Username)
    		fmt.Printf("%T",unmarshal)
    	}
    }	
    
    • 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

    制作插件

    我们上文中提到,生成go用到了一个protoc的外部插件。插件的工作原理其实就是读取proto文件内容,并根据文件内容生成指定文件。文件中保存了定义的字段类型和方法之间的关系,以便编码和解码时使用

    当我们在proto文件中定义了service,说明我们需要用到rpc服务。protoc内置的插件和一些官方的外部插件,只是提供了service中方法与参数类型的绑定,用于校验调用方法时类型是否正确。在service中定义的方法,我们需要自己创建。

    当方法较多时,我们需要打开proto文件,一一对应的创建func,效率不高也容易出错。这种情况下,我们制作一个插件,读取service内容,自动创建方法,方法内的代码之后根据业务需求填充即可。

    go官方提供了一个包,可以自动解析读取命令行传入proto文件,代码如下

    
    package main
    
    import (
    	"fmt"
    	"strings"
    	"google.golang.org/protobuf/compiler/protogen"
    )
    
    type rpc struct{}
    
    func main() {
    	g := rpc{}
    	protogen.Options{}.Run(g.Generate)
    }
    
    
    // Generate generate service code
    func (md *rpc) Generate(plugin *protogen.Plugin) error {
    	//遍历读取的命令行中传入的proto文件
    	for _, f := range plugin.Files {
    		//如果文件中没有定义service,跳过
    		if len(f.Services) == 0 {
    			continue
    		}
    		//根据proto文件名,生成一个自定义的文件,保存该proto中定义的func
    		fileName := f.GeneratedFilenamePrefix + ".svr.go"
    		//把该文件保存在proto文件的所在目录
    		t := plugin.NewGeneratedFile(fileName, f.GoImportPath)
    		//写入文字
    		t.P("// Code generated by protoc-gen-tinyrpc.")
    		//写入空行
    		t.P()
    		//写入包名
    		pkg := fmt.Sprintf("package %s", f.GoPackageName)
    		t.P(pkg)
    		t.P()
    		//遍历一个文件下所有service,自动生成方法
    		for _, s := range f.Services {
    			//插入注释,定义service类型
    			serviceCode := fmt.Sprintf(`%stype %s struct{}`,
    				getComments(s.Comments), s.Desc.Name())
    			t.P(serviceCode)
    			t.P()
    			//遍历一个service下的方法,生成方法
    			for _, m := range s.Methods {
    				funcCode := fmt.Sprintf(`%sfunc(this *%s) %s(args *%s,reply *%s)error{
    					// define your service ...
    					return nil
    				}
    				`, getComments(m.Comments), s.Desc.Name(),
    					m.Desc.Name(), m.Input.Desc.Name(), m.Output.Desc.Name())
    				t.P(funcCode)
    			}
    		}
    	}
    	return nil
    }
    
    // getComments get comment details
    func getComments(comments protogen.CommentSet) string {
    	c := make([]string, 0)
    	c = append(c, strings.Split(string(comments.Leading), "\n")...)
    	c = append(c, strings.Split(string(comments.Trailing), "\n")...)
    
    	res := ""
    	for _, comment := range c {
    		if strings.TrimSpace(comment) == "" {
    			continue
    		}
    		res += "//" + comment + "\n"
    	}
    	return res
    }
    
    
    • 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
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75

    执行 go install 编译该文件,生成exe文件,文件名称设置为protoc-gen-gofunc.exe。然后再次执行protoc命令,如下

    protoc --go_out=. proto_type/hello.proto --gofunc_out=. proto_type/hello.proto

    执行后,会在proto_type目录下生成两个文件,hello.pd.go, hello.srv.go

  • 相关阅读:
    ts3.接口和对象类型
    CleanMyMac是什么软件?有哪些特色功能?
    vue2 + ts + vuex 构建脚手架 ( 实现一个 存储笔记功能 )
    python基于django教学作业管理系统(源码+系统+mysql数据库+Lw文档)
    Map集合继承结构
    应广PMC131 SOP16 16pin八位单片机
    探秘OpenCV中的findContours函数
    5G 技术、云原生开发和机器学习是推动物联网解决方案的重要助力
    UEFI实战——图形化
    解决 ARouter 无法生成路由表,Toast提示 找不到目标路由
  • 原文地址:https://blog.csdn.net/u012830303/article/details/126517355