从Protocol Buffers 到 gRPC

来源:互联网 发布:远程软件xt800 编辑:程序博客网 时间:2024/05/24 03:22

从Protocol Buffers 到 gRPC

标签: ProtoBuf gRPC HTTP/2

我们项目中准备使用Protocol Buffers来进行服务器和客户端的消息交互,采用gRPC开源框架,服务器使用Java,客户端有Android和iOS。


  • 从Protocol Buffers 到 gRPC
    • 一Protocol Buffers
      • 文档
      • 使用
        • 1 定义一个消息类型 官方例子
        • 2 字段限制
        • 3 Tags
        • 4 具体使用
      • Protoc源码的编译以及使用
        • 1 安装ProtocolBuffer工具
        • 2 使用protoc编译proto文件
    • 二gRPC
      • 文档
      • 使用
        • 1 定义一个消息以及RPC服务 官方例子
        • 2 具体使用
      • gRPC源码的编译以及使用
    • 三gRPC 在不同平台上的使用方法
      • Android
        • 1 相关配置
        • 2 相关使用
        • 3 相关链接
      • iOS
        • 1 相关编译和安装
        • 2 相关使用
        • 3 相关Objective-C代码示例
        • 4 测试
        • 5 问题汇总
        • 6 相关链接
      • More usages to be continued
    • 四与ProtoBuf相关的其它
      • ProtoBufJSONXML 格式互转 Java
        • 1 简述
        • 2 示例代码
        • 3 相关链接
    • 五与gRPC相关的其它
      • 文件上传分块
        • 1 简述
        • 2 实现步骤
        • 3 示例代码Java
        • 4 未完待续
    • 六To be continued


一、Protocol Buffers

Protocol Buffers是google的一个开源项目,它是用于结构化数据串行化的灵活、高效、自动的方法,例如XML/JSON,不过它比XML/JSON更小、更快、也更简单。你可以定义自己的数据结构,然后使用代码生成器生成的代码来读写这个数据结构。你甚至可以在无需重新部署程序的情况下更新数据结构。

1. 文档

关于Protocol Buffers的语法、使用、源码、编译方法等,可参考以下官方链接:

Protocol Buffers 使用指南
Protocol Buffers 源码 on Github

2. 使用

2.1 定义一个消息类型 (官方例子)

// [START declaration]syntax = "proto3";package tutorial;// [END declaration]// [START java_declaration] protoc编译后生成的java包结构名以及外部调用类名option java_package = "com.example.tutorial";option java_outer_classname = "AddressBookProtos";// [END java_declaration]// [START csharp_declaration]option csharp_namespace = "Google.Protobuf.Examples.AddressBook";// [END csharp_declaration]// [START messages]message Person {  string name = 1;  int32 id = 2;  // Unique ID number for this person.  string email = 3;  enum PhoneType {    MOBILE = 0;    HOME = 1;    WORK = 2;  }  message PhoneNumber {    string number = 1;    PhoneType type = 2;  }  repeated PhoneNumber phones = 4;}// Our address book file is just one of these.message AddressBook {  repeated Person people = 1;}// [END messages]

2.2 字段限制

字段限制共有3类:

required:必须赋值的字段(proto3中不声明的字段默认为required) 。
optional:可有可无的字段。
repeated:可重复字段(变长字段),类似于数组。

由于一些历史原因,repeated字段并没有想象中那么高效,新版本中允许使用特殊的选项来获得更高效的编码:

repeated int32 samples = 4 [packed=true];

2.3 Tags

我们从message的定义看到,消息中的每一个字段后面都跟着一个数值。而这个数值作为这个字段在message中的唯一标示(Tag),序列化时相当于key-value键值对中的key。
在使用时应该将1-15留给频繁使用的字段,因为1-15使用一个字节编码,16-2047使用2个字节编码。可以指定的最小的Tag为1,最大为2291(即536,870,911),但是不能使用19000-19999之间的值,因为这些值是预留值,强行使用会导致编译失败。

2.4 具体使用

编写好proto文件后将其编译成对应语言的类文件(下面会讲解使用protoc编译),具体调用我们以Java为例,大致如下:

// 新建一个Person对象Person kido =  Person.newBuilder()    .setId(1234)    .setName("Kido")    .setEmail("everlastxgb@gmail.com")    .addPhone(      Person.PhoneNumber.newBuilder()        .setNumber("10086")        .setType(Person.PhoneType.HOME))    .build();// 写对象OutputStream outputStream;//...kido.writeTo(outputStream);// 读对象byte[] data;InputStream inputStream;//...Person.parseFrom(data).toBuilder();// orPerson.parseFrom(inputStream).toBuilder();

……

更多的使用和说明,请查看相关官方说明文档,此处不再赘述… 下面来讲一下protoc源码的编译以及使用。

3. Protoc源码的编译以及使用

3.1 安装ProtocolBuffer工具

3.1.1 下载源码

git clone git@github.com:google/protobuf.git

注: 也可以从protobuf/releases下载最新的release代码。

3.1.2 执行自动化脚本
下载完成后cd到工程目录下,运行autogen脚本。
PB依赖autoconf、automake、libtool、curl,在各个平台上安装这些依赖即可。比如在mac上,用brew安装:
(如果你的mac没安装brew,那么请先安装brew,其实也很简单,如下,命令行执行以下脚本)。

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

安装brew成功后使用”brew install”就可以很方便安装我们想要的其它套件了。

brew install autoconf automake libtool curl

安装完成后执行以下脚本:

./autogen.sh

在运行这个脚本的时候,如果遇到gmock下载被墙的问题,点击这个地址手动下载即可: gmock-1.7.0.zip 下载之后,将它丢到源代码目录即可。同时将脚本中的

# curl $curlopts -O https://googlemock.googlecode.com/files/gmock-1.7.0.zip 

注释掉再执行即可。

3.1.3 编译

$ ./configure$ make$ make check$ sudo make install

查看是否安装完毕

protoc --version

3.2 使用protoc编译.proto文件

针对编译生成不同语言,有cpp_out, java_out, objc_out等,目前PB支持的语言见下表:

Language Flag C++ (include C++ runtime and protoc) cpp_out Java java_out Python python_out Objective-C objc_out C# csharp_out JavaNano javanano_out JavaScript js_out Ruby ruby_out Go go_out PHP php_out

这里我们假设编译“addressbook.proto”生成对应的java文件。则命令行cd到该proto文件所在目录,执行:

protoc --java_out=. addressbook.proto

运行的时候,如若遇到xxx找不到的问题,则安装即可。例如在mac上出现“pkg-config找不到”的问题,则执行:

brew install pkg-config

二、gRPC

gRPC是一个高性能、通用的开源RPC框架,其由Google主要面向移动应用开发并基于HTTP/2协议标准而设计,基于ProtoBuf(Protocol Buffers)序列化协议开发,且支持众多开发语言。gRPC提供了一种简单的方法来精确地定义服务和为iOS、Android和后台支持服务自动生成可靠性很强的客户端功能库。客户端充分利用高级流和链接功能,从而有助于节省带宽、降低的TCP链接次数、节省CPU使用、和电池寿命。

1. 文档

关于gRPC的语法、使用、源码、编译方法等,可参考以下官方链接:

gRPC的介绍和使用指南
gRPC 源码 on Github

2. 使用

gRPC支持多种语言,并能够基于语言自动生成客户端和服务端功能库。目前,在GitHub上已提供了C版本grpc、Java版本grpc-java 和 Go版本grpc-go,其它语言的版本正在积极开发中,其中 grpc支持C、C++、Node.js、Python、Ruby、Objective-C、PHP和C#等语言,grpc-java也已经支持Android开发。

通信方式有几种,如下:

  • Simple RPC
  • Server-side streaming RPC
  • Client-side streaming RPC
  • Bidirectional streaming RPC

下面以Simple RPC为例简单演示一下

2.1 定义一个消息以及RPC服务 (官方例子)

syntax = "proto3";option java_package = "io.grpc.examples";package helloworld;// The greeter service definition.service Greeter {  // Sends a greeting  rpc SayHello (HelloRequest) returns (HelloReply) {}}// The request message containing the user's name.message HelloRequest {  string name = 1;}// The response message containing the greetingsmessage HelloReply {  string message = 1;}

2.2 具体使用

编写好proto文件后将其编译成对应语言的类文件(下面会讲解使用protoc+grpc plugin编译),具体调用我们以Java为例,大致如下:

Client 端:

// ...import io.grpc.ManagedChannel;import io.grpc.ManagedChannelBuilder;import io.grpc.examples.helloworld.nano.GreeterGrpc;import io.grpc.examples.helloworld.nano.HeadRequest;import io.grpc.examples.helloworld.nano.HelloReply;import io.grpc.examples.helloworld.nano.HelloRequest;// ...String mHost = "192.168.1.11";String mPort = "50051";ManagedChannel mChannel = ManagedChannelBuilder.forAddress(mHost, mPort)                        .usePlaintext(true)                        .build();sayHello(mChannel);// ...private String sayHello(ManagedChannel channel) {    GreeterGrpc.GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(channel);    HelloRequest message = new HelloRequest();    message.name = mMessage;    HelloReply reply = stub.sayHello(message);    return reply.message;}

Server 端:

// ...import io.grpc.Server;import io.grpc.ServerBuilder;import io.grpc.stub.StreamObserver;// ...Server server = ServerBuilder.forPort(port)        .addService(new GreeterImpl())        .build()        .start();// ...private class GreeterImpl extends GreeterGrpc.AbstractGreeter {    @Override    public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {      HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();      responseObserver.onNext(reply);      responseObserver.onCompleted();    }  }

……

更多的使用和说明,请查看相关官方说明文档,此处不再赘述… 下面来讲一下gRPC源码的编译以及使用。

3. gRPC源码的编译以及使用

(事实上只有当我们需要修改gRPC相关源码的时候,才需要在修改后对其进行编译操作。)

3.1 以gRPC-Java为例

3.1.1 下载源码

$ git clone https://github.com/grpc/grpc-java.git

3.1.2 编译Java工程依赖的jar包

如果要编译proto文件自动生成gRPC代码,除了必备的protoc之外,还需要一个代码生成器插件(对于java,是protoc-gen-grpc-java),如果我们不需要重新编译其中的proto文件,就不需要去编译生成这个插件。想要在编译的时候跳过这点,仅需在工程根目录下新建gradle.properties文件,然后添加skipCodegen=true即可。

skipCodegen=true,表示“Skipping the build of codegen and compilation of proto files”

下载完成后cd到工程目录下,命令行输入如下:

$ ./gradlew build 

编译顺利的话,可以在对应的文件夹(all, netty, okhttp, protobuf等)的build文件夹里找到生成的lib。

当然,如果你想把编译好的jar添加到你的本地库,以方便后续工程的依赖引用,可以执行以下命令:

$ ./gradlew install

3.1.3 protoc-gen-grpc-java 的编译和使用

# 进入grpc-java的工程根目录:$ cd $GRPC_JAVA_ROOT/compiler# 编译插件$ ../gradlew java_pluginExecutable# 测试插件是否ok$ ../gradlew test

若上述运行成功,代表插件成功生成,可以在$GRPC_JAVA_ROOT/compiler/build/exe/java_plugin中看到”protoc-gen-grpc-java” 。那么,接下来可以配合protoc将.proto文件生成对应的java代码文件了:

# To compile a proto file and generate Java interfaces out of the service definitions:$ protoc --plugin=protoc-gen-grpc-java=build/exe/java_plugin/protoc-gen-grpc-java \  --grpc-java_out="$OUTPUT_FILE" --proto_path="$DIR_OF_PROTO_FILE" "$PROTO_FILE"# To generate Java interfaces with protobuf nano:$ protoc --plugin=protoc-gen-grpc-java=build/exe/java_plugin/protoc-gen-grpc-java \  --grpc-java_out=nano:"$OUTPUT_FILE" --proto_path="$DIR_OF_PROTO_FILE" "$PROTO_FILE"

同样的,如果你想把编译好的codegen插件添加到你的本地库,以方便后续工程的依赖引用,可以执行以下命令:

$ ../gradlew install

关于上述的protobuf和gRPC的相关编译,我是在MAC-OS下执行,按照我编写的步骤一般都很顺利。


三、gRPC 在不同平台上的使用方法

上述内容对ProtoBuf和gRPC的语法和使用进行了简单的说明,而重点讲解了其源码编译这一块。如果你看得云里雾里,没关系,使用起来其实很简单。(详细的使用请参考文中提到的官方文档地址)

1. Android

1.1 相关配置

可以选择将proto文件放置于src/main/proto文件夹或其他你想要的位置。

然后在Project的build.gradle中添加protobuf-gradle-plugin:

 dependencies {        classpath 'com.android.tools.build:gradle:1.5.0'        classpath "com.google.protobuf:protobuf-gradle-plugin:0.7.4"    }

在主Module中添加:

apply plugin: 'com.google.protobuf'// ...protobuf {    protoc {        artifact = 'com.google.protobuf:protoc:3.0.0-beta-2'    }    plugins {        grpc {            artifact = 'io.grpc:protoc-gen-grpc-java:0.14.0'         }    }    generateProtoTasks {        all().each { task ->            task.builtins {                javanano {                    // Options added to --javanano_out                    option 'ignore_services=true'                }            }            task.plugins {                grpc {                    // Options added to --grpc_out                    option 'nano'                }            }        }    }}dependencies {    compile 'com.google.code.findbugs:jsr305:3.0.0'    compile 'com.google.guava:guava:18.0'    compile 'com.squareup.okhttp:okhttp:2.2.0'    compile 'javax.annotation:javax.annotation-api:1.2'    compile 'io.grpc:grpc-okhttp:0.14.0'    compile 'io.grpc:grpc-protobuf-nano:0.14.0'     compile 'io.grpc:grpc-stub:0.14.0' }

很智能的,添加上述依赖后,Rebuild Project后,会自动帮你生成java文件。关于protobuf-gradle-plugin的源码以及更多声明和使用方法可参见以下链接:
https://github.com/google/protobuf-gradle-plugin

1.2 相关使用

关键代码演示如下:

// ...import io.grpc.ManagedChannel;import io.grpc.ManagedChannelBuilder;import io.grpc.examples.helloworld.nano.GreeterGrpc;import io.grpc.examples.helloworld.nano.HeadRequest;import io.grpc.examples.helloworld.nano.HelloReply;import io.grpc.examples.helloworld.nano.HelloRequest;// ...// // 实际使用请替换成grpc server监听的真实"host:port"String mHost = "0.0.0.0";String mPort = "50051";ManagedChannel mChannel = ManagedChannelBuilder.forAddress(mHost, mPort)                        .usePlaintext(true)                        .build();sayHello(mChannel);// ...private String sayHello(ManagedChannel channel) {    GreeterGrpc.GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(channel);    HelloRequest message = new HelloRequest();    message.name = mMessage;    HelloReply reply = stub.sayHello(message);    return reply.message;}

1.3 相关链接

更多关于Android(Java)的使用和说明,请查看相关官方说明文档,此处不再赘述…

gRPC-Java 源码 on Github
Android examples on Github
gRPC- Java 使用指南

2. iOS

2.1 相关编译和安装

一切开始之前,请先下载grpc的源码:

 $ git clone https://github.com/grpc/grpc.git $ cd grpc $ git submodule update --init

鉴于后面我们将运行C++语言的Server例子做测试,所以此处我们顺便可以先make编译安装gRPC C Core library:

 $ make $ [sudo] make install

我们想要根据proto生成Objective-C的代码,需要先安装protoc + protoc-gen-objcgrpc,安装方法有几种,如下:

1.在线安装

在Mac OS X上,请先安装homebrew。然后执行以下命令安装protoc和gRPC protoc插件:(需翻墙)

$ curl -fsSL https://goo.gl/getgrpc | bash -

2.本地安装

既然我们有源码,就要随时做好对源码的修改以及编译的准备。
首先,安装protoc,参考上述”Protocol Buffer 3.1节”。
接着,cd到源码根目录下,编译protoc的gRPC相关插件:

make plugins

执行成功后,会看到根目录的 bins/opt 下面生成对应不同语言的”grpc_xxx_plugin”,此处我们只需要用到”grpc_objective_c_plugin”,将其链接到环境变量:

ln -s `pwd`/bins/opt/grpc_objective_c_plugin /usr/local/bin/protoc-gen-objcgrpc

2.2 相关使用

上述工具依赖安装好之后,我们来讲一下使用。且看Github上面的HelloWorld例子:

cd examples/objective-c/helloworld

可以看到,官方示例中采用CocoaPods依赖的方式。
我们先安装:

brew install cocoapods

接着重点关注一下两个文件 “Podfile” 和 “HelloWorld.podspec”

Podfile

可以看到示例的Podfile是本地依赖。

source 'https://github.com/CocoaPods/Specs.git'platform :ios, '8.0'pod 'Protobuf', :path => "../../../third_party/protobuf"pod 'BoringSSL', :podspec => "../../../src/objective-c"pod 'gRPC', :path => "../../.."target 'HelloWorld' do  # Depend on the generated HelloWorld library.  pod 'HelloWorld', :path => '.'end

当然我们也可以按照需要改为在线依赖:

可先使用pod search搜索对应的依赖的线上版本:

pod search Protobuf# - Versions: 3.0.0-beta-2, 3.0.0-alpha-4.1, 3.0.0-alpha-3 [master repo]pod search BoringSSL# - Versions: 3.0, 2.0, 1.0 [master repo]pod search gRPC# - Versions: 0.13.0, 0.12.0, 0.11.1, 0.6.0, 0.5.1, 0.0.2 [master repo]

知道版本号后,可以对应修改Podfile,大概示例如下:

...pod 'Protobuf', '~>3.0.0-beta-2'pod 'BoringSSL', '~>3.0'pod 'gRPC', '~>0.13.0'...# 事实上,由于我们已有HelloWorld.podspec写好了引用,所以实际上述依赖根本不用写...(可以直接注释)

HelloWorld.podspec

通过”pod search”我们可以知道Protobuf线上最新release版本是”3.0.0-beta-2”,grpc线上最新release版本是”0.13.0”,所以这里进行了相应的版本号修改:

Pod::Spec.new do |s|  s.name     = "HelloWorld"  s.version  = "0.0.1"  s.license  = "New BSD"  s.ios.deployment_target = "7.1"  s.osx.deployment_target = "10.9"  # Base directory where the .proto files are.  src = "../../protos"  # Directory where the generated files will be placed.  dir = "Pods/" + s.name  # Run protoc with the Objective-C and gRPC plugins to generate protocol messages and gRPC clients.  s.prepare_command = <<-CMD    mkdir -p #{dir}    protoc -I #{src} --objc_out=#{dir} --objcgrpc_out=#{dir} #{src}/helloworld.proto  CMD  s.subspec "Messages" do |ms|    ms.source_files = "#{dir}/*.pbobjc.{h,m}", "#{dir}/**/*.pbobjc.{h,m}"    ms.header_mappings_dir = dir    ms.requires_arc = false    ms.dependency "Protobuf", "~> 3.0.0-beta-2"  end  s.subspec "Services" do |ss|    ss.source_files = "#{dir}/*.pbrpc.{h,m}", "#{dir}/**/*.pbrpc.{h,m}"    ss.header_mappings_dir = dir    ss.requires_arc = true    ss.dependency "gRPC", "~> 0.13.0"    ss.dependency "#{s.name}/Messages"  endend

上述podspec例子中的.proto文件是放置在工程外部目录,实际使用我们可以放在工程目录,方便管理和编译。同时,实际上我们真实项目中的.proto文件可能有多个,而且会分目录存放。针对这种情况,大概示例如下:

  s.prepare_command = "protoc --objc_out=. --objcgrpc_out=. *.proto **/*.proto"  ...    ms.source_files = "*.pbobjc.{h,m}", "**/*.pbobjc.{h,m}"  ...    ss.source_files = "*.pbrpc.{h,m}", "**/*.pbrpc.{h,m}"

理解完这两个文件之后,就到了安装生成对应OC代码的步骤了,在HelloWorld目录下,执行:
(理解完之后,你会发现其实Podfile中只需要引用HelloWorld.podspec即可)

pod install# 若想避免不必要的更新cocoapods的spec仓库,可适当追加一些参数,如下:pod install --verbose --no-repo-update# 但若Podfile依赖改动了,则记得要更新pod update

安装成功后可以看到目录下生成了”Podfile.lock”文件和”Pods”文件夹(里面就是对应的OC代码)

安装过程部分内容涉及翻墙的,所以最好开启VPN

2.3 相关Objective-C代码示例

#import <UIKit/UIKit.h>#import "AppDelegate.h"#import <GRPCClient/GRPCCall+ChannelArg.h>#import <GRPCClient/GRPCCall+Tests.h>#import <HelloWorld/Helloworld.pbrpc.h>// 实际使用请替换成grpc server监听的真实"host:port"static NSString * const kHostAddress = @"0.0.0.0:50051";int main(int argc, char * argv[]) {  @autoreleasepool {    [GRPCCall useInsecureConnectionsForHost:kHostAddress];    [GRPCCall setUserAgentPrefix:@"HelloWorld/1.0" forHost:kHostAddress];    HLWGreeter *client = [[HLWGreeter alloc] initWithHost:kHostAddress];    HLWHelloRequest *request = [HLWHelloRequest message];    request.name = @"Objective-C";    [client sayHelloWithRequest:request handler:^(HLWHelloReply *response, NSError *error) {      NSLog(@"%@", response.message);    }];    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));  }}

2.4 测试

Objective-C成功运行后,我们想要测试其与Server的通信是否成功,则需要一个Server端的服务。我们且用官方的C++代码实现的helloworld server:

# 假设当前处于grpc工程根目录cd examples/cpp/helloworld/make./greeter_server# 运行成功会出现"Server listening on 0.0.0.0:50051"# 可以顺带另开窗口执行C++ Client试试./greeter_client# 运行成功会输出"Greeter received: Hello world"

server端开启监听后,运行上述我们编写的ObjC代码,如果看到在控制台有log输出“Hello Objective-C”,证明成功通信。

如果你make失败,请确认是否已经’sudo make install’ 安装了gRPC C Core library。

2.5 问题汇总

  1. pod install 成功后打开工程编译出现error:“…different version of protoc which is incompatible with your Protocol Buffer library…”的问题。

    答: 很明显是protoc编译器和依赖的库版本不一致的问题。由于我安装protoc是使用master最新代码,而依赖的protobuf库是release的3.0.0-beta-2,也就是说我的protoc版本太新了(30001)。

    解决方法: 下载release的protobuf 3.0.0-beta-2版本重新编译安装protoc。步骤如下:

    # 进入protobuf工程根目录make        make install
  2. pod install 时出现“… –objcgrpc_out: protoc-gen-objcgrpc: Plugin killed by signal 11.”

    答: protoc-gen-objcgrpc跟当前的protoc版本冲突问题。由于我重新安装了protoc,而没重新对应编译grpc生成protoc-gen-objcgrpc。

    解决方法: 重新编译grpc,重新生成protoc-gen-objcgrpc插件并链接到本地。步骤如下:

    # 进入grpc工程根目录make cleanmake        make installmake pluginsrm /usr/local/bin/protoc-gen-objcgrpcln -s `pwd`/bins/opt/grpc_objective_c_plugin /usr/local/bin/protoc-gen-objcgrpc

2.6 相关链接

更多关于Objective-C的使用和说明,请查看相关官方说明文档,此处不再赘述…

gRPC 源码 on Github
Objective-C examples on Github
gRPC- Objective-C 使用指南

3. More usages to be continued.


四、与ProtoBuf相关的其它

1. ProtoBuf/JSON/XML 格式互转 (Java)

1.1 简述:

可以实现ProtoBuf(byte array)与其他文本格式的XML、JSON、HTML之间的转换。
下载protobuf-java-format-1.2.jar导入工程即可。

1.2 示例代码:

[XmlFormat],proto对象转xml

Message someProto = SomeProto.getDefaultInstance();XmlFormat xmlFormat = new XmlFormat();String asXml = xmlFormat.printToString(someProto);

[XmlFormat],xml转proto对象

Message.Builder builder = SomeProto.newBuilder();String asXml = _load xml document from a source_;XmlFormat xmlFormat = new XmlFormat();xmlFormat.merge(asXml, builder);

[JsonFormat],proto对象转json

Message someProto = SomeProto.getDefaultInstance();JsonFormat jsonFormat = new JsonFormat();String asJson = jsonFormat.printToString(someProto);

[JsonFormat],json转proto对象

Message.Builder builder = SomeProto.newBuilder();String asJson = _load json document from a source_;JsonFormat jsonFormat = new JsonFormat();jsonFormat.merge(asJson, builder);

[HtmlFormat],proto对象转html

Message someProto = SomeProto.getDefaultInstance();HtmlFormat htmlFormat = new HtmlFormat();String asHtml = htmlFormat.printToString(someProto);

1.3 相关链接:

protobuf-java-format-1.2.jar
protobuf-java-format source on google code


五、与gRPC相关的其它

1. 文件上传分块

1.1 简述:

Google官方有这么一句话,如下:

Protocol Buffers are not designed to handle large messages. As a general rule of thumb, if you are dealing in messages larger than a megabyte each, it may be time to consider an alternate strategy.

大概意思就是:“Protocol Buffers不是为了处理数据量大的信息而设计的。按照经验来说,如果你准备用它处理大于1M的数据信息,你需要考虑使用一种替代策略。”

看到这个,不要慌,还是有解决方案的,官方也说了:

That said, Protocol Buffers are great for handling individual messages within a large data set. Usually, large data sets are really just a collection of small pieces, where each small piece may be a structured piece of data. Even though Protocol Buffers cannot handle the entire set at once, using Protocol Buffers to encode each piece greatly simplifies your problem: now all you need is to handle a set of byte strings rather than a set of structures.
……

大概意思就是:“…你可以把一个很大的数据块分割为若干个小数据块…”

事实上,我觉得解决方案其实有两个:
①. 按上述所说,把一个很大的数据块分割为若干个小数据块。
②. 先用httpmime去上传文件得到文件的url(或者其它标志id之类的),再将该url(or id)放到message传输。

不过,既然是统一整改,就不要这么混杂地使用第二种了,直接使用第一种分割方案其实也很简单。

1.2 实现步骤:

文件分bytes + Client-side streaming

1.3 示例代码(Java):

(此处只是简单演示实现逻辑,实际具体应用需考虑周全。)

proto定义:

syntax = "proto3";option java_multiple_files = true;option java_package = "io.grpc.kido.fileuploader";option java_outer_classname = "FileUploader";option objc_class_prefix = "KFL";package fileuploader;service Uploader {  rpc uploadFile (stream FileRequest) returns (FileReply) {}}// The request message containing part of the file.message FileRequest {   int64 offset = 1;// 当前分块的起始点相对于整个文件的位置   bytes data = 2; // 当前分块的文件字节数组}// The response message containing the greetingsmessage FileReply {  int32 status = 1;  string message = 2;}

Client 端:

// 关键代码private void uploadFile(File file) throws Exception {    StreamObserver<FileReply> responseObserver = new StreamObserver<FileReply>() {        @Override        public void onNext(FileReply reply) {        }        @Override        public void onError(Throwable t) {        }        @Override        public void onCompleted() {        }    };    StreamObserver<FileRequest> requestObserver = asyncStub.uploadFile(responseObserver);    try {        FileRequest fileRequest = new FileRequest();        BufferedInputStream bInputStream = new BufferedInputStream(new FileInputStream(file));        int bufferSize = 512 * 1024; // 512k        byte[] buffer = new byte[bufferSize];        int tmp = 0;        int size = 0;        if ((tmp = bInputStream.read(buffer)) > 0) {            size += tmp;            fileRequest.data = buffer;            fileRequest.offset = size;            requestObserver.onNext(fileRequest);        }    } catch (Exception e) {        // Cancel RPC        requestObserver.onError(e);        throw e;    }    // Mark the end of requests    requestObserver.onCompleted();}

Server 端:

// 关键代码    public static void main(String[] args) throws Exception {        UploaderServer server = new UploaderServer(8980);        server.start();        server.blockUntilShutdown();    }    private static class UploaderService extends UploaderGrpc.AbstractUploader {        UploaderService() {        }        @Override        public StreamObserver<FileRequest> uploadFile(StreamObserver<FileReply> responseObserver) {            return new FileRequestObserver(responseObserver);        }    }    static class FileRequestObserver implements StreamObserver<FileRequest> {        private int status = 200;        private String message = "";        private BufferedOutputStream bufferedOutputStream = null;        private StreamObserver<FileReply> responseObserver = null;        public FileRequestObserver(final StreamObserver<FileReply> responseObserver) {            try {                this.responseObserver = responseObserver;                bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("md5filename"));            } catch (Exception e) {            }        }        @Override        public void onNext(FileRequest fileRequest) {            byte[] data = fileRequest.getData().toByteArray();            long offset = fileRequest.getOffset();            try {                if (bufferedOutputStream != null) {                    bufferedOutputStream.write(data); // 或者这里先接收保存,到时再一次写。(但会占内存)                }            } catch (Exception e) {            }            // write the file to the server by bufferStream        }        @Override        public void onError(Throwable throwable) {            String error = throwable.getMessage();            logger.log(Level.WARNING, "onError->" + error);            status = 500;            message = error;        }        @Override        public void onCompleted() {            responseObserver.onNext(            FileReply.newBuilder()                .setMessage(message)                .setStatus(status)                .build()            );            responseObserver.onCompleted();            try {                if (bufferedOutputStream != null) {                    bufferedOutputStream.flush();                    bufferedOutputStream.close();                }            } catch (Exception e) {            }        }    }

1.4 未完待续


六、To be continued.

1 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 群拉人失败了怎么办 安装解释包错误怎么办 皮皮麻将进不去怎么办 郑州集体户口怎么办准生证 广州集体户口准生证怎么办 学校寄档案丢失怎么办 深圳通儿童卡怎么办 集体户没有户口卡怎么办 北京滴滴跑长途怎么办 应届毕业生落户成都档案怎么办 执业医师缺考怎么办 国家级考试缺考怎么办 异地就业后档案怎么办 没有工作报到证怎么办 网络系统管理员封锁网络怎么办 公房没买断产权怎么办 去异地工作社保怎么办 深圳辞职了 社保怎么办 小产权孩子上学怎么办 多余的粽叶怎么办 农村两处宅基地怎么办 农村一户多宅怎么办 无锡未满五年安置房怎么办 人才公寓退休后怎么办 套餐到期不用了怎么办 日本手机自动续约怎么办 如果购房后退房怎么办 土地权70年到期怎么办 在亚庇丢了护照怎么办 回国丢了护照怎么办 大学挂科拿不到毕业证怎么办 孩子脸上长黑痣怎么办 婴儿tsh10.6偏高怎么办 苹果x显示edge怎么办 煮的鸡肉腥怎么办 卫生间蹲坑太高怎么办 鸡肉为什么太腥怎么办 房间有屎臭味怎么办 word转pdf乱码怎么办 脸上扣的疤怎么办 孩子洗澡后发烧怎么办