什么是 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 代码生成工具的包中,有一个这样的目录,一致以来都没搞明白是做什么用的,如下图:

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

而这些描述文件中最重要的文件 就是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 tree
  optional 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 static 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文件就能知道。以下是我自己在项目中使用的生成代码类 供大家参考

using Google.Protobuf.Compiler;using Google.Protobuf.Reflection;using System;using System.Collections.Generic;using System.IO;using System.Linq;using System.Text;namespace DotBPE.ProtobufPlugin{    public class DotbpeGen
    {        public static void Generate(CodeGeneratorRequest request, CodeGeneratorResponse response)        {            foreach (var protofile in request.ProtoFile)
            {                try{
                    GenerateByProtoFile(protofile, response);
                }                catch(Exception ex){                    using (Stream stream = File.Create("./error.txt"))
                    {                        byte[] err = Encoding.UTF8.GetBytes(ex.Message+ex.StackTrace);
                        stream.Write(err,0,err.Length);
                    }
                    response.Error += ex.Message;
                }
            }
        }        private static void GenerateSourceInfo(FileDescriptorProto protofile, CodeGeneratorResponse response)        {            bool genericDoc;
            protofile.Options.CustomOptions.TryGetBool(DotBPEOptions.GENERIC_MARKDOWN_DOC,out genericDoc);            if (!genericDoc)
            {                return;
            }
            StringBuilder sb = new StringBuilder();            foreach (var location in protofile.SourceCodeInfo.Location)
            {                string path = String.Join(",", location.Path);                string span = String.Join(",", location.Span);                string leadingDetachedComments = String.Join("\r", location.LeadingDetachedComments);                string trailingComments = String.Join("\r", location.TrailingComments);
                sb.AppendLine("{\"Path\",\""+path+"\",\"Span\",\""+span+"\",\"LeadingComments\",\""+ location.LeadingComments + "\",\"LeadingDetachedComments\",\""+ leadingDetachedComments + "\",\"TrailingComments\",\""+ trailingComments + "\"}");
                
            }            var nfile = new CodeGeneratorResponse.Types.File
            {
                Name = GetFileName(protofile.Name) + "SI.txt",
                Content = sb.ToString()
            };
            response.File.Add(nfile);
        }        private static void GenerateByProtoFile(FileDescriptorProto protofile, CodeGeneratorResponse response)        {
            GenerateSourceInfo(protofile, response);
            GenerateServer(protofile, response);
            GenerateClient(protofile, response);

        }        private static void GenerateServer(FileDescriptorProto protofile, CodeGeneratorResponse response)        {            bool genericServer;
            protofile.Options.CustomOptions.TryGetBool(DotBPEOptions.DISABLE_GENERIC_SERVICES_SERVER, out genericServer);            if (genericServer)
            {                return;
            }            if (protofile.Service == null || protofile.Service.Count <= 0) return;            //生成文件头
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("// Generated by the protocol buffer compiler.  DO NOT EDIT!");
            sb.AppendLine($"// source: {protofile.Name}");            //还可以生成注释

            sb.AppendLine("#region Designer generated code");
            sb.AppendLine("");
            sb.AppendLine("using System; ");
            sb.AppendLine("using System.Threading.Tasks; ");
            sb.AppendLine("using DotBPE.Rpc; ");
            sb.AppendLine("using DotBPE.Protocol.Amp; ");
            sb.AppendLine("using Google.Protobuf; ");
            sb.AppendLine("");            string ns = GetFileNamespace(protofile);
            sb.AppendLine("namespace " + ns + " {");            //生成代码
            foreach (ServiceDescriptorProto t in protofile.Service)
            {
                t.Options.CustomOptions.TryGetBool(DotBPEOptions.DISABLE_GENERIC_SERVICES_SERVER, out genericServer);                if (genericServer)
                {                    continue;
                }

                sb.AppendLine("");
                sb.AppendLine("//start for class Abstract"+t.Name);
                GenerateServiceForServer(t, sb);
                sb.AppendLine("//end for class Abstract"+t.Name);
            }
            sb.AppendLine("}\n");
            sb.AppendLine("#endregion\n");            var nfile = new

http://www.cnblogs.com/xuanye/p/6752872.html

网友评论