使用CSharp编写Google Protobuf插件

什么是 Google Protocol Buffer?

Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准,目前已经正在使用的有超过 48,162 种报文格式定义和超过 12,183 个 .proto 文件。他们用于 RPC 系统和持续数据存储系统。

Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 多种语言的API,包括C++、 C# 、GO、 JAVA、 PYTHON

如果你并不了解Protobuf能做什么,建议结合google搜索关键字,看一下入门级别的文章,或者看一下官方文档中的Developer Guide,或者中文的开发指南 .官方的文档中有各种语言相关的示例,可以结合代码看一下实际的用法。

很多人说为什么不用json(或者xml), 答案很简单,Protobuf更小,更简洁,而且序列化和反序列化更快!

谷歌最新开源的gRpc框架就是默认使用Protobuf作为数据传输格式和服务描述文件。对于gRpc 就不做详细介绍了,有兴趣的可以看一下官网。

言归正传,在实际使用Protobuf过程中,我发现Protobuf不但可以编写描述消息(Message)的内容,同时可以表述其他方法(类似Rpc中的方法),主要是gRpc中看到的。同时在Protobuf 代码生成工具的包中,有一个这样的目录,一致以来都没搞明白是做什么用的,如下图:

frameborder="0" scrolling="no" style="border-width: medium; width: 460px; height: 370px;">

在目录中存在大量已经定义好的proto文件,其实这些文件是Protobuf的描述文件,类似元数据。用本身的语法描述本身,同时通过这些文件生成对应的语言的元数据类等代码,比如在C#版本的Google.Protobuf中就能看到上述描述文件生成的类,如下图所示

frameborder="0" scrolling="no" style="border-width: medium; width: 554px; height: 504px;">

而这些描述文件中最重要的文件 就是descriptor.proto 这个文件,这个文件是整个proto语法的描述类,描述了实际Protobuf各层次语法的结构,来一起看一下这个文件的一些代码, 上面这个代码描述了proto文件定义的语法定义,如前面两个字段意思是可选的name,可选的package字段,中间是描述可多个message_type(Message),service(Rpc Service) ,enum_type(枚举)等定义,然后一层层分解下去。 基本上就可以了解Protobuf语法的全貌和扩展点了

message FileDescriptorProto {optional string name = 1;       // file name, relative to root of source treeoptional string package = 2;    // e.g. "foo", "foo.bar", etc.// Names of files imported by this file.repeated string dependency = 3;  // Indexes of the public imported files in the dependency list above.repeated int32 public_dependency = 10;  // Indexes of the weak imported files in the dependency list.// For Google-internal migration only. Do not use.repeated int32 weak_dependency = 11;  // All top-level definitions in this file.repeated DescriptorProto message_type = 4;repeated EnumDescriptorProto enum_type = 5;repeated ServiceDescriptorProto service = 6;repeated FieldDescriptorProto extension = 7;optional FileOptions options = 8;  // This field contains optional information about the original source code.// You may safely remove this entire field without harming runtime// functionality of the descriptors -- the information is needed only by// development tools.optional SourceCodeInfo source_code_info = 9;  // The syntax of the proto file.// The supported values are "proto2" and "proto3".optional string syntax = 12;
}

同时在compiler目录下 还有一个plugin的目录,其中的plugin.proto文件很耐人寻味,先来看下这个文件中的内容

syntax = "proto3";package google.protobuf.compiler;option java_package = "com.google.protobuf.compiler";option java_outer_classname = "PluginProtos";option csharp_namespace = "Google.Protobuf.Compiler";option go_package = "plugin_go";import "google/protobuf/descriptor.proto";message CodeGeneratorRequest {  repeated string file_to_generate = 1;  string parameter = 2;  repeated FileDescriptorProto proto_file = 15;
}message CodeGeneratorResponse {  string error = 1; message File {    string name = 1;    string insertion_point = 2;    string content = 15;}  repeated File file = 15;
}

删除了非必要的注释后,我们可以看到这个文件里面其实只定义了两个类型,一个是代码生成请求,一个是代码生成响应,而在CodeGeneratorRequest中又有之前我们在descriptor.proto中看到的FileDescriptorProto 这个类的信息,用大腿都可以想到这里应该就是代码生成插件获取元数据的入口了,那么怎么做呢?

从gRpc 的代码生成示例中 我们可以看到 其实Protobuf是支持自定义生成代码插件的,如下所示:

%PROTOC% -I../../protos --csharp_out Greeter  ../../protos/helloworld.proto --grpc_out Greeter --plugin=protoc-gen-grpc=%PLUGIN%

按理我们可以实现自己的插件来生成我们需要的任意格式,包括各种代码,甚至是文档。但是这个资料却非常少,几乎没有多少相关的文章,后来终于找到一片关于plugin的文章http://www.expobrain.net/2015/09/13/create-a-plugin-for-google-protocol-buffer/ ,大家有兴趣的可以看看,不过文章的重点是这句:

The core part is the interface code to read a request from the stdin, traverse the AST and write the response on the stdout.

原来插件的接口代码其实是从标准输入中读取流,然后再把你要生成的内容输出到标准输出中。这些终于知道怎么用了。。

撩起袖子开始干,通过protoc命令行生成plugin.proto的代码

protoc-I../../protos --csharp_out test  ../../protos/plugin.proto

新建一个控制台项目,把代码copy 到项目中,并在Program.cs代码中添加测试的代码

using Google.Protobuf;
using Google.Protobuf.Compiler;
using System;

namespace DotBPE.ProtobufPlugin{  
 class Program{        static void Main(string[] args)      
 
{Console.OutputEncoding = System.Text.Encoding.UTF8;  
         var response = new CodeGeneratorResponse();      
               try{CodeGeneratorRequest request;      
         using (var inStream = Console.OpenStandardInput()){request = CodeGeneratorRequest.Parser.ParseFrom(inStream);}ParseCode(request, response);}            catch (Exception e){response.Error += e.ToString();}            using (var output = Console.OpenStandardOutput()){response.WriteTo(output);output.Flush();}}    
            private stat void ParseCode(CodeGeneratorRequest request, CodeGeneratorResponse response)        {DotbpeGen.Generate(request,response);}
             }

哈哈 开始编译,然而编译不通过!,坑爹啊! 原来C#版本中 Google.Protobuf已经生成好的类 都是internal访问权限,不能从外部引用。。。但是Google.Protobuf是开源的。。而且我需要用的类 我也可以通过protoc命令自己生成到同一个项目中,或者设置成public访问权限。。方便起见,我直接copy了Google.Protobuf的源码到我们的项目中,这次再次编译 ,代码就完美运行了,接下来的工作 不过是填充DotbpeGen.Generate 的代码了,这不过是体力活。

至于CodeGeneratorRequest和CodeGeneratorResponse 到底有什么方法,其实看proto文件就能知道。以下是我自己在项目中使用的生成代码类 供大家参考,然后我们编写一个proto文件测试以下

//benchmark.protosyntax = "proto3";package dotbpe;option csharp_namespace = "DotBPE.IntegrationTesting";import public "dotbpe_option.proto";option optimize_for = SPEED;//Benchmark测试服务service BenchmarkTest{option (service_id)= 50000 ;//设定服务ID//测试发送Echo消息rpc Echo (BenchmarkMessage) returns (BenchmarkMessage){option (message_id)= 1 ;//设定消息ID};//Echo尾部的注释// 测试发送退出消息rpc Quit (Void) returns (Void){option (message_id)= 10000 ;//设定消息ID};//Quit尾部的注释}//我是void消息message Void {}//我是BenchmarkMessage消息message BenchmarkMessage {  //字段前的注释string field1 = 1; //字段后的注释//字段前的注释 多行//字段前的字数多行int32 field2 = 2; //字段后的注释/**  * 字段前注释特殊格式  * 字段前注释特殊格式多行  */int32 field3 = 3;  string field4 = 4;repeated fixed64 field5 = 5;  string field9 = 9;  string field18 = 18;  
bool field80 = 80;  
bool field81 = 81;  
int32 field280 = 280 ;
 int32 field6 = 6;
  int64 field22 = 22 ;  
  bool field59 = 59 ;  
  string field7 = 7;  
  int32 field16 = 16 ;  
  int32 field130 = 130 ;
   bool field12 = 12 ;
    bool field17 = 17;
      bool field13 = 13;  
      bool field14 = 14;
     int32 field104 = 104 ;
      int32 field100 = 100 ;
      int32 field101 = 101 ;
      string field102 = 102;  
      string field103 = 103;
      int32 field29 = 29 ;  
      bool field30 = 30 ;  
      int32 field60 = 60 ;  
      int32 field271 = 271 ;  
      int32 field272 = 272;
      int32 field150 = 150;
      int32 field23 = 23;
      bool field24 = 24;  
      int32 field25 = 25 ;
      bool field78 = 78 ;  
      int32 field67 = 67;
      int32 field68 = 68 ;  
      int32 field128 = 128 ;
      string field129 = 129 ;
      int32 field131 = 131 ; }
// dotbpe_option.proto// [START declaration]syntax = "proto3";package dotbpe;
// [END declaration]// [START csharp_declaration]option csharp_namespace = "DotBPE.ProtoBuf";
// [END csharp_declaration]import "google/protobuf/descriptor.proto";
//扩展服务extend google.protobuf.ServiceOptions {  
int32 service_id = 51001;
 bool disable_generic_service_client = 51003; //是否生成客户端代码bool disable_generic_service_server = 51004; //是否生成服务端代码} extend google.protobuf.MethodOptions {  int32 message_id = 51002; }extend google.protobuf.FileOptions {  bool disable_generic_services_client = 51003;
//是否生成客户端代码bool disable_generic_services_server = 51004; //是否生成服务端代码bool generic_markdown_doc = 51005; //是否生成文档}

上面的dotbpe_option.proto 我们proto文件进行了自定义的扩展,添加一些自己需要的额外信息,其实所有扩展都是对descriptor.proto中消息的扩展。

然后我们通过命令来生成一下,这里有个特殊的约定,一定要注意当我们设置

protoc-gen-dotbpe=../../tool/ampplugin/dotbpe_amp.exe 插件的名称protoc-gen-dotbpe时,那么输出的目录一定要写成--dotbpe_out ,两个名字一点要匹配哦

set -excd $(dirname $0)/../../test/IntegrationTesting/PROTOC=protoc
PLUGIN=protoc-gen-dotbpe=../../tool/ampplugin/dotbpe_amp.exe
IntegrationTesting_DIR=./DotBPE.IntegrationTesting/$PROTOC 
 -I=./protos --csharp_out=$IntegrationTesting_DIR --dotbpe_out=$IntegrationTesting_DIR \./protos/benchmark.proto  --plugin=$PLUGIN

差不多就结束了,相关的代码可以在https://github.com/xuanye/dotbpe/tree/develop/src/tool 查看到,这是我最近在写的一个C#的rpc框架,现在完成了基本的功能,还需要进一步完善,有机会再介绍把。

descriptor.proto信息挖掘

我们注意到在descriptor.proto文件中包含有这样的一个message: SourceCodeInfo, 这个消息体里有如下字段

 optional string leading_comments = 3; optional string trailing_comments = 4;repeated string leading_detached_comments = 6;

这是非常有意思的定义,意思是可以在运行时获取到proto文件中的注释。这可以帮助我们生成 文档或者代码注释,但是读取逻辑比较复杂,其内部有一个通过Path和Span来定位元素的逻辑。因为在实际的情况中,一般都是要获取Service和Message上的注释,那么就来专门讨论一下如何获取这两个类型的注释吧。

下面是 SourceCodeInfo.Location 中我们需要用到Path示例

 * [4, m] - Message的注释* [4, m, 2, f] - Message 中 字段(field)的注释* [6, s] - Service的注释* [6, s, 2, r] - ServiceRpc方法的注释

where:

  • m - proto文件中Message的索引(就是第几个定义的Message), 从0开始

  • f - Message中Field字段的索引(就是第几个字段), 从0开始

  • s - proto文件中Service的索引, 从0开始

  • r - Service中Rpc方法的索引, 从0开始

like this:

// [4, 0] 就是这里的注释 message MyMessage {  // [4, 0, 2, 0] 在这里int32 field1 = 1; // [4, 0, 2, 0] 也在这里}// [4, 0] 就是这里的注释 

// [6, 0] 在这里!service MyService {  // [6, 0, 2, 0] 在这里!rpc (MyMessage) returns (MyMessage); }

想要了解全部内容可以去看下descriptor.proto中的注释内容 吧

原文地址:http://www.cnblogs.com/xuanye/p/6752872.html


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/325536.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

jsp使用cookie实现记住用户名和密码

首先说一下实现的功能: 用户打开注册页面,最下面有个记住用户名和密码的复选框,如果勾选上,则在登录页面会自动将用户名和密码赋值到文本框中,使用java中的cookie实现,下面就是代码: 注册页面代…

集群、分布式、负载均衡区别

转载自 集群、分布式、负载均衡区别 参考:http://virtualadc.blog.51cto.com/3027116/615836” 集群 集群的概念 计算机集群通过一组松散集成的计算机软件和/或硬件连接起来高度紧密地协作完成计算工作。在某种意义上,他们可以被看作是一台计算机。集…

万圣节之夜

万 圣 节 昨天两位班主任就说今天班级有活动,从今天下午就开始了。18级下午都在布置教室,19级暂时没有行动。午休醒来之后,一脸高兴的去3班上课去。一进教室,同学们都在趴着睡觉呢。有点安静的不习惯了都。其中有个前排的女同学脸…

EL表达式和Jstl常见的用法

一、使用EL表达式获取集合中的数据&#xff1a; <%Map names new HashMap();names.put("one", "1");names.put("two", "2");request.setAttribute("names", names);int a 2;request.setAttribute("a", a);Us…

线上防雪崩利器——熔断器设计原理与实现

转载自 线上防雪崩利器——熔断器设计原理与实现 本文来自作者投稿&#xff0c;作者林湾村龙猫&#xff0c;这是一篇他根据工作中遇到的问题总结出的最佳实践。 上周六&#xff0c;我负责的业务在凌晨00-04点的支付全部失败了。 结果一查&#xff0c;MD&#xff0c;晚上银行…

ASP.NET Core 菜鸟之路:从Startup.cs说起

1.前言 本文主要是以Visual Studio 2017 默认的 WebApi 模板作为基架&#xff0c;基于Asp .Net Core 1.0&#xff0c;本文面向的是初学者&#xff0c;如果你有 ASP.NET Core 相关实践经验&#xff0c;欢迎在评论区补充。与早期版本的 ASP.NET 对比&#xff0c;最显著的变化之一…

el表达式与jstl的用法

课上顺便整理了下java中的El表达式和jstl的用法&#xff0c;下面以举例的方式来阐述各个标签的作用&#xff1a;一、 使用el表达式将Map集合中的数据显示出来&#xff1a;先给Map集合里面放一些数据库&#xff0c;通过EL表达式显示在页面中&#xff1a;<%Map names new Has…

业务太复杂?教你如何降低软件的复杂性

转载自 业务太复杂&#xff1f;教你如何降低软件的复杂性 John Ousterhout 是斯坦福大学计算机系教授&#xff0c;也是 Tcl 语言的创造者。 今年四月&#xff0c;他出版了一本新书《软件设计的哲学》&#xff08;A Philosophy of Software Design&#xff09;。这是课程讲稿…

[翻译]在 .NET Core 中的并发编程

原文地址:http://www.dotnetcurry.com/dotnet/1360/concurrent-programming-dotnet-core 今天我们购买的每台电脑都有一个多核心的 CPU&#xff0c;允许它并行执行多个指令。操作系统通过将进程调度到不同的内核来发挥这个结构的优点。然而&#xff0c;还可以通过异步 I/O 操作…

JS中函数和变量声明的提升

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>01_变量提升与函数提升</title> </head> <body> <!-- 1. 变量声明提升* 通过var定义(声明)的变量, 在定义语句之前就可以访问到*…

记录学生的日常

最近比较忙&#xff0c;都没时间更新公众号了&#xff0c;粉丝每天都在减&#xff0c;哈哈哈。最近19级的学生们在做网页设计静态网页项目&#xff0c;从上周五到现在&#xff0c;班内除了两个小组比较慢之外&#xff0c;其余的进度都还可以&#xff0c;从做项目中就可以看出来…

Mono新突破:CentOS 7.2下安装Mono 5.0

微软Build2017大会期间.NET领域的.NET core之外&#xff0c;就是Visual Studio For Mac&#xff0c;大家都知道Visual Studio For Mac 是基于Mono运行的&#xff0c;Mono 5.0也是闪亮登场&#xff0c;Mono 5.0是一个非常重要的里程碑版本&#xff0c;支持Windows 64位部署&…

搞定 JVM 垃圾回收就是这么简单

转载自 搞定 JVM 垃圾回收就是这么简单 JVM的垃圾回收机制是Java中比较重要的知识点&#xff0c;也是面试官常考的问题&#xff0c;本文主要围绕以下面试题来讲解JVM的垃圾回收机制。 问题答案在文中都有提到 如何判断对象是否死亡&#xff08;两种方法&#xff09;。 简单…

一份感动到哭的成绩单……

今天对班级内进行了测试&#xff0c;这是自实行周周考以来&#xff0c;第三次测试了&#xff0c;还记得第一次的测试&#xff0c;几分的&#xff0c;十几分的&#xff0c;几十分的五花八门&#xff0c;成绩可算是惨不忍睹啊。第二次测试&#xff0c;开发的进步了好多&#xff0…

深刻理解:C#中的委托、事件

C#中的事件还真是有点绕啊&#xff0c;以前用JavaScript的我&#xff0c;理解起来还真是废了好大劲&#xff01;刚开始还真有点想不明白为什么这么绕&#xff0c;想想和JS的区别&#xff0c;最后终于恍然大悟&#xff01; C#中事件绕的根本原因&#xff1a; C#的方法&#xff…

mybatis中,collection配置后查询只显示一条记录

描述一下问题&#xff1a; 已知有两个表&#xff0c;一个是user表&#xff0c;一个是address,一&#xff08;user&#xff09;对多(address)的关系&#xff0c;在user的实体类里面写属性&#xff1a; private List<Address> addressList new ArrayList<Address>(…

Java中的List你真的会用吗

转载自 Java中的List你真的会用吗 List是Java中比较常用的集合类&#xff0c;关于List接口有很多实现类&#xff0c;本文就来简单介绍下其中几个重点的实现ArrayList、LinkedList和Vector之间的关系和区别。 List List 是一个接口&#xff0c;它继承于Collection的接口。它…

Android 全局字体设置 例如楷体

1、在res下新建资源文件目录font&#xff0c;把字体文件拷贝到font文件夹中 2、在AndroidManifest.xml中的application节点下&#xff0c;设置全局style&#xff0c;引入字体文件 <item name"android:fontFamily">font/pingfang_sc_regular</item>或者

.Net Core中使用ref和Spanamp;lt;Tamp;gt;提高程序性能

一、前言 其实说到ref&#xff0c;很多同学对它已经有所了解&#xff0c;ref是C# 7.0的一个语言特性&#xff0c;它为开发人员提供了返回本地变量引用和值引用的机制。Span 也是建立在ref语法基础上的一个复杂的数据类型&#xff0c;在文章的后半部分&#xff0c;我会有一个例…

微服务为什么选Spring Cloud

转载自 微服务为什么选Spring Cloud 现如今微服务架构十分流行&#xff0c;而采用微服务构建系统也会带来更清晰的业务划分和可扩展性。同时&#xff0c;支持微服务的技术栈也是多种多样的&#xff0c;本系列文章主要介绍这些技术中的翘楚——Spring Cloud。这是序篇&#x…