动态使用proto文件

一般情况下,使用 Protobuf 的人们都会先写好 .proto 文件,再用 Protobuf 编译器生成目标语言所需要的源代码文件。将这些生成的代码和应用程序一起编译。

可是在某且情况下,人们无法预先知道.proto 文件,他们需要动态处理一些未知的 .proto 文件。比如一个通用的消息转发中间件,它不可能预知需要处理怎样的消息。这需要动态编译 .proto 文件,并使用其中的 Message。

Protobuf 提供了 google::protobuf::compiler 包来完成动态编译的功能。主要的类叫做 importer,定义在 importer.h 中。使用 Importer 非常简单,下图展示了与 Import 和其它几个重要的类的关系。


图2. Importer 类
 

Import 类对象中包含三个主要的对象,分别为处理错误的MultiFileErrorCollector 类,定义 .proto 文件源目录的 SourceTree 类。

下面还是通过实例说明这些类的关系和使用吧。

对于给定的 proto 文件,比如 lm.helloworld.proto,在程序中动态编译它只需要很少的一些代码。如代码清单 6 所示。


清单 6. 代码

                                                                                

 google::protobuf::compiler::MultiFileErrorCollector errorCollector;

 google::protobuf::compiler::DiskSourceTree sourceTree;

 

 google::protobuf::compiler::Importer importer(&sourceTree, &errorCollector);

 sourceTree.MapPath("", protosrc);

 

 importer.import(“lm.helloworld.proto”);

 

首先构造一个 importer 对象。构造函数需要两个入口参数,一个是 source Tree 对象,该对象指定了存放 .proto 文件的源目录。第二个参数是一个 error collector 对象,该对象有一个 AddError 方法,用来处理解析 .proto 文件时遇到的语法错误。

之后,需要动态编译一个 .proto 文件时,只需调用 importer 对象的 import 方法。非常简单。

那么我们如何使用动态编译后的 Message呢?我们需要首先了解几个其他的类

Packagegoogle::protobuf::compiler 中提供了以下几个类,用来表示一个 .proto 文件中定义的 message,以及 Message 中的 field,如图所示。


图 3. 各个 Compiler 类之间的关系
 

类 FileDescriptor 表示一个编译后的 .proto 文件;类 Descriptor 对应该文件中的一个 Message;类 FieldDescriptor 描述一个 Message 中的一个具体 Field。

比如编译完lm.helloworld.proto 之后,可以通过如下代码得到 lm.helloworld.id 的定义:


清单 7. 得到 lm.helloworld.id 的定义的代码

                                                                                

 const protobuf::Descriptor *desc =

    importer_.pool()->FindMessageTypeByName(“lm.helloworld”);

 const protobuf::FieldDescriptor* field =

    desc->pool()->FindFileByName (“id”);

 

通过 Descriptor,FieldDescriptor 的各种方法和属性,应用程序可以获得各种关于Message 定义的信息。比如通过 field->name() 得到 field 的名字。这样,您就可以使用一个动态定义的消息了。

编写新的 proto 编译器

随 Google ProtocolBuffer 源代码一起发布的编译器 protoc 支持 3 种编程语言:C++,java 和 Python。但使用 Google Protocol Buffer 的 Compiler 包,您可以开发出支持其他语言的新的编译器。

类 CommandLineInterface封装了 protoc 编译器的前端,包括命令行参数的解析,proto文件的编译等功能。您所需要做的是实现类 CodeGenerator 的派生类,实现诸如代码生成等后端工作:

程序的大体框架如图所示:


图 4. XML 编译器框图
 

在 main() 函数内,生成 CommandLineInterface 的对象 cli,调用其 RegisterGenerator() 方法将新语言的后端代码生成器 yourG 对象注册给 cli 对象。然后调用 cli 的Run() 方法即可。

这样生成的编译器和 protoc 的使用方法相同,接受同样的命令行参数,cli 将对用户输入的 .proto 进行词法语法等分析工作,最终生成一个语法树。该树的结构如图所示。


图 5. 语法树
 

其根节点为一个 FileDescriptor对象(请参考“动态编译”一节),并作为输入参数被传入 yourG 的 Generator() 方法。在这个方法内,您可以遍历语法树,然后生成对应的您所需要的代码。简单说来,要想实现一个新的 compiler,您只需要写一个 main 函数,和一个实现了方法 Generator() 的派生类即可。

在本文的下载附件中,有一个参考例子,将.proto 文件编译生成 XML 的 compiler,可以作为参考。


RPCX 支持使用 ProtoBuf 协议作为编码协议,可以通过定义 ProtoBuf 文件来实现 RPCX 的服务调用。 以下是一个简单的示例,演示了如何使用 ProtoBuf 文件定义服务接口和消息类型,并通过 RPCX 进行调用。 服务端代码: ```go package main import ( "fmt" "github.com/smallnest/rpcx/server" "google.golang.org/protobuf/proto" "io/ioutil" "log" "net/http" ) type HelloService struct{} func (t *HelloService) SayHello(request []byte, reply *[]byte) error { req := new(Request) err := proto.Unmarshal(request, req) if err != nil { return err } res := &Response{Message: "Hello, " + req.Name + "!"} *reply, err = proto.Marshal(res) return err } func main() { s := server.NewServer() s.RegisterName("HelloService", new(HelloService), "") http.Handle("/rpc", s) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "index.html") }) fmt.Println("starting server at :8080") log.Fatal(http.ListenAndServe(":8080", nil)) } ``` 服务端使用 HTTP 协议进行服务调用,通过 `RegisterName` 函数注册了一个名为 `HelloService` 的服务,并将其绑定到 `http://localhost:8080/rpc` 上。 ProtoBuf 文件定义了服务接口和消息类型: ```protobuf syntax = "proto3"; package hello; message Request { string name = 1; } message Response { string message = 1; } service HelloService { rpc SayHello (Request) returns (Response); } ``` 客户端代码: ```go package main import ( "fmt" "github.com/smallnest/rpcx/client" "google.golang.org/protobuf/proto" "io/ioutil" "log" "net/http" ) func main() { d := client.NewHTTPDiscovery("http://localhost:8080/rpc") xclient := client.NewXClient("HelloService", client.Failtry, client.RandomSelect, d, client.DefaultOption) defer xclient.Close() req := &Request{Name: "World"} data, err := proto.Marshal(req) if err != nil { log.Fatalf("marshal error: %v", err) } res := new(Response) err = xclient.Call(nil, "SayHello", data, res) if err != nil { log.Fatalf("call error: %v", err) } fmt.Println(res.Message) } ``` 客户端使用 HTTP 协议进行服务调用,通过 `NewHTTPDiscovery` 函数创建一个 RPCX 客户端实例。客户端调用 `Call` 方法来调用服务端的 `SayHello` 方法,并将参数和返回值转换为 ProtoBuf 格式。 在客户端代码,需要使用 `protoc` 工具将 ProtoBuf 文件生成对应的 Go 代码,然后将生成的代码文件引入到客户端代码。具体的使用方法可以参考 ProtoBuf 官方文档。 这个示例演示了如何使用 ProtoBuf 文件定义服务接口和消息类型,并通过 RPCX 进行调用。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值