Protobuf3 google protobuf

筅森魡賤 提交于 2019-12-05 00:42:35

Protobuf3

相关连接:

Google-protobuf(需要翻墙):https://developers.google.com/protocol-buffers/

Protobuf on github:https://github.com/google/protobuf

Git上的资源:https://github.com/google/protobuf/releases

Proto3协议格式:https://developers.google.com/protocol-buffers/docs/proto3

前言:protobuf是什么?

Protocol Buffers是一个跨语言、跨平台的具有可扩展机制的序列化数据工具。也就是说,我在ubuntu下用python语言序列化一个对象,并使用http协议传输到使用java语言的android客户端,java使用对用的代码工具进行反序列化,也可以得到对应的对象。听起来好像跟json没有多大区别。。。其实区别挺多的。

Google说protobuf是smaller,faster,simpler,我们使用google规定的proto协议定义语言,之后使用proto的工具对代码进行“编译”,生成对应的各个平台的源代码,我们可以使用这些源代码进行工作。

 

Proto2与proto3:

Proto2和proto3有些区别,包括proto语言的规范,以及生成的代码,proto3更好用,更简便,所以我们直接存proto3开始。

 

下载需要的资源:

1:proto编译工具

2:你需要的语言的依赖包

这些在https://github.com/google/protobuf/releases都可以下载,windows平台的编译工具使用protoc-3.0.0-beta-2-win32.zip这个压缩包里的exe可执行文件,mac和ubuntu平台可使用源码进行编译安装,具体步骤见https://github.com/google/protobuf下面的README.md.

 

定义你的协议:

1.建立协议文件,例如test.proto

2.写入测试内容,//代表注释

 

//我沿袭了上家公司的定义规范(在此谢谢曹老师)

syntax = "proto3";

package com.proto.test;

 

//定义规范:

//message类 均首字母大写   大写!!!!

 

//test url :   /test

message Request10000 {         //定义一个类,名称是Request10000 用于客户端请求

Params params = 1;

message Params {

string name = 1;                 //一些测试内容

int64 timestamp = 2;               //时间戳 毫秒

repeated string usernames = 3;   //一些测试内容

}

}

 

message Response10000 {     //定义一个类,名称是Response10000 用于响应客户端请求

Data data = 1;

message Data {

//返回的数据

string helloname = 1;                   //一些测试内容

string timestamp = 2;                   //时间戳 毫秒

repeated string helloUsername = 3;      //一些测试内容

}

}

 

3.编译(免责声明:以上代码是从一个项目里拷出来并删减的,没有测试过)

Windows平台下,新建gen.bat文件,输入内容:

protoc -I=. --python_out=gen_python *.proto

 

protoc -I=. --java_out=gen_java *.proto

protoc -I=. --javanano_out=gen_javanano *.proto

protoc -I=. --objc_out=gen_objc *.proto

保存。并把gen.bat、test.proto、protc.exe(上面下载的编译工具)放到同一个目录下,并在该目录下新建对应的输出文件夹,gen_java、gen_javanano等,双击gen.bat就会自动编译,如果成功的话,这些输出文件夹里就应该有对应的生成的代码了。Tip:为了查看编译时可能由于协议定义语法不正确导致的错误,我一般在cmd命令行下,进入该目录,输入gen.bat回车 编译,如果报错,下面可以看到错误信息。

 

4.使用:

 

4.1:服务器使用。

以java后台为例,java后台一般应使用java_out对应的输出代码,也即gen_java目录下的文件,这个文件里面的封装方式:无法针对每个对象的每个字段进行更改,只能通过builder构造返回对象,能够保证客户端数据的完整性。Java客户端可以用javanano_out,可以针对每个字段操作,方便一些。

把gen_java中的包导入工程后,你会发现报了一对错,因为你有一些依赖包需要导入。首先是proto底层的一些文件,这些都可以在protobuf-3.0.0源码包中找到,还有一些,他们的.proto源码可以在protobuf-3.0.0-beta-1\src\google\protobuf目录下找到,手动编译一下,倒入工程即可使用。我当初从源码中找这些依赖文件找了好久= =。再有就是一些google的其他jar包:guava.jar,gson-parent.jar。为了方便使用,我把proto底层的一些依赖包打包成了jar文件。

后台使用代码示例:

@RequestMapping("/test")

public String test(HttpServletRequest request,  

            HttpServletResponse response){

System.out.println("start at action");

LazyStringList names = null;

String name = "";

long timestamp = 0;

LazyStringArrayList outNames = new LazyStringArrayList();

try {

Request10000 requ = Request10000.parseFrom(request.getInputStream());

names = (LazyStringList) requ.getParams().getUsernamesList();

System.out.println(requ.getParams().getName());

name = requ.getParams().getName();

timestamp = requ.getParams().getTimestamp();

System.out.println("success insert at action");

catch (IOException e) {

e.printStackTrace();

Response10000.Builder builder = Response10000.newBuilder();

Response10000 resp = builder.build();

try {

response.getOutputStream().write(resp.toByteArray());

return "dassds";

catch (IOException e1) {

e1.printStackTrace();

}

}

System.out.println("success at action");

//builder data

Response10000.Data.Builder builder3 = Response10000.Data.newBuilder();

builder3.setHelloname("hello" + name);

builder3.setTimestamp("send timestamp is " + timestamp);

if(names != null){

for(int i = 0; i < names.size() ; i++){

outNames.add("hello" + names.get(i));

}

}

builder3.addAllHelloUsername(outNames);

Response10000.Data data = builder3.build();

//builde response

Response10000 resp = Response10000.newBuilder()

.setData(data)

.build();

try {

response.getOutputStream().write(resp.toByteArray());

catch (IOException e) {

e.printStackTrace();

}

return "hahahah";

}

使用protobuf的话,我们就不需要返回值了,定义返回类型为void即可。客户端也是使用类似的情况进行解析、请求。

上面是之前的一条测试协议,直接复制下来删减的,运行不起来的免责声明。

步骤:1.过去请求的输入流,使用对应proto类的parseFrom方法进行解析,解析成java对象,2.进行一系列的验证、查询、等服务端操作后,3.通过proto类的Builder类进行构造返回的对象,并调用该对象的toByteArray方法写入到输出流中。

 

4.2客户端ios使用(下面是我的一个逗比同事写的)

iOS 开发  使用protro buffer 与后台配工作经验

 

步骤:

1.  首先我们需要用cocoapods 倒入protro 包 。

2.把倒入进入的文件 修改成非arc

3.上面修改完毕之后,如果还有报错不要惊慌,是重复定义,细心去查找重复的删除就行

 

 

请求步骤

1.我使用的ASIHTTPRequest 去请求 的protro buffer。

2.用ASIHTTPRequest 里面自带的传参数方法 把我们要传的参数转化成data 到后台

3.如果用 AFNETWOrking  请求可以自行设置代理 转化字符byte 传入后台

4.解析的时候。后台会直接给你返回一个model  但是为了耦合度 不要去直接用哪个model  可以重新定义一个咱们自己的model 然后去接收,再去运用。

5.解析时候一定要用返回的response 中的方法 一定要给response  用[response parsefromdata: error :];方法 给转换一下再去用我们自己定义的model接受。

 

4.3客户端android

public static byte[] postWithProtobuf(MessageNano params, String url,

int cmdid, long timestamps) {

// ��POST�ķ�ʽ�������

RequestBody formBody = null;

try {

com.squareup.okhttp.Request.Builder requestBuilder = new com.squareup.okhttp.Request.Builder();

// header���֣�Ĭ��Ҫ��cmdid��timestamp

requestBuilder.addHeader(Constant.HEADER_CMDID, cmdid + "");

requestBuilder

.addHeader(Constant.HEADER_TIMESTAMP, timestamps + "");

// body���֣��ö����ƴ���

formBody = RequestBody.create(MediaType.parse("application/octet-stream")

, MessageNano.toByteArray(params));

requestBuilder.url(url);

if (formBody != null) {

requestBuilder.post(formBody);

}

 

com.squareup.okhttp.Request requestOk = requestBuilder.build();

Response response = client.newCall(requestOk).execute();

if (response.isSuccessful()) {

return response.body().bytes();

}

} catch (Exception e) {

e.printStackTrace();

}

return null;

}

以上是使用okhttp写的静态方法,用于发起请求和接收返回字节数组。

调用代码:

Request10001 request = new Request10001();

//设置request的内容

 

byte[] result = RequestUtil.postWithProtobuf(request,

RequestUrl.URL_REGISTER, cmdid, timestamp);

System.out.println("End at " + System.currentTimeMillis());

if (null != result) {

try {

Response10001 response = Response10001

.parseFrom(result);

//解析,解析完后直接取response中的字段即可。

 

前后端开发的时候直接看.proto文件即可知道协议是怎么定义的了。。。

有什么问题可以查看google-protobuf官方文档,https://developers.google.com/protocol-buffers/docs/proto3下面给出了一些proto变量类型的定义以及在各个语言中的对应的类型。下面给附上:同时附上google官网上的一些英文介绍。

.proto Type

Notes

C++ Type

Java Type

Python Type[2]

Go Type

Ruby Type

C# type

double

 

double

double

float

float64

Float

double

float

 

float

float

float

float32

Float

float

int32

Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead.

int32

int

int

int32

Fixnum or Bignum (as required)

int

int64

Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead.

int64

long

int/long[3]

int64

Bignum

long

uint32

Uses variable-length encoding.

uint32

int[1]

int/long[3]

uint32

Fixnum or Bignum (as required)

uint

uint64

Uses variable-length encoding.

uint64

long[1]

int/long[3]

uint64

Bignum

ulong

sint32

Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s.

int32

int

int

int32

Fixnum or Bignum (as required)

int

sint64

Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s.

int64

long

int/long[3]

int64

Bignum

long

fixed32

Always four bytes. More efficient than uint32 if values are often greater than 228.

uint32

int[1]

int

uint32

Fixnum or Bignum (as required)

uint

fixed64

Always eight bytes. More efficient than uint64 if values are often greater than 256.

uint64

long[1]

int/long[3]

uint64

Bignum

ulong

sfixed32

Always four bytes.

int32

int

int

int32

Fixnum or Bignum (as required)

int

sfixed64

Always eight bytes.

int64

long

int/long[3]

int64

Bignum

long

bool

 

bool

boolean

boolean

bool

TrueClass/FalseClass

bool

string

A string must always contain UTF-8 encoded or 7-bit ASCII text.

string

String

str/unicode[4]

string

String (UTF-8)

string

bytes

May contain any arbitrary sequence of bytes.

string

ByteString

str

[]byte

String (ASCII-8BIT)

ByteString

介绍

This guide describes how to use the protocol buffer language to structure your protocol buffer data, including.proto file syntax and how to generate data access classes from your .proto files. It covers the proto3version of the protocol buffers language: for information on the older proto2 syntax, see the Proto2 Language Guide.

This is a reference guide – for a step by step example that uses many of the features described in this document, see the tutorial for your chosen language (currently proto2 only; more proto3 documentation is coming soon).

Defining A Message Type

First let's look at a very simple example. Let's say you want to define a search request message format, where each search request has a query string, the particular page of results you are interested in, and a number of results per page. Here's the .proto file you use to define the message type.

Hahaah,if you continue read english things,i will tell you my qq(970135203).Any questions you can ask me by qq, and i will tell you every thing I know about protobuf3.

syntax = "proto3";

 

message SearchRequest {

  string query = 1;

  int32 page_number = 2;

  int32 result_per_page = 3;

}

· The first line of the file specifies that you're using proto3 syntax: if you don't do this the protocol buffer compiler will assume you are using proto2. This must be the first non-empty, non-comment line of the file.

· The SearchRequest message definition specifies three fields (name/value pairs), one for each piece of data that you want to include in this type of message. Each field has a name and a type.

Specifying Field Types

In the above example, all the fields are scalar types: two integers (page_number and result_per_page) and a string (query). However, you can also specify composite types for your fields, including enumerations and other message types.

Assigning Tags

As you can see, each field in the message definition has a unique numbered tag. These tags are used to identify your fields in the message binary format, and should not be changed once your message type is in use. Note that tags with values in the range 1 through 15 take one byte to encode, including the identifying number and the field's type (you can find out more about this in Protocol Buffer Encoding). Tags in the range 16 through 2047 take two bytes. So you should reserve the tags 1 through 15 for very frequently occurring message elements. Remember to leave some room for frequently occurring elements that might be added in the future.

The smallest tag number you can specify is 1, and the largest is 229 - 1, or 536,870,911. You also cannot use the numbers 19000 though 19999 (FieldDescriptor::kFirstReservedNumber throughFieldDescriptor::kLastReservedNumber), as they are reserved for the Protocol Buffers implementation - the protocol buffer compiler will complain if you use one of these reserved numbers in your .proto. Similarly, you cannot use any previously reserved tags.

Specifying Field Rules

Message fields can be one of the following:

· singular: a well-formed message can have zero or one of this field (but not more than one).

· repeated: this field can be repeated any number of times (including zero) in a well-formed message. The order of the repeated values will be preserved.

For historical reasons, repeated fields of scalar numeric types aren't encoded as efficiently as they could be. New code should use the special option [packed=true] to get a more efficient encoding. For example:

repeated int32 samples = 4 [packed=true];

You can find out more about packed encoding in Protocol Buffer Encoding.

Adding More Message Types

Multiple message types can be defined in a single .proto file. This is useful if you are defining multiple related messages – so, for example, if you wanted to define the reply message format that corresponds to yourSearchResponse message type, you could add it to the same .proto:

message SearchRequest {

  string query = 1;

  int32 page_number = 2;

  int32 result_per_page = 3;

}

 

message SearchResponse {

 ...

}

Adding Comments

To add comments to your .proto files, use C/C++-style // syntax.

message SearchRequest {

  string query = 1;

  int32 page_number = 2;  // Which page number do we want?

  int32 result_per_page = 3;  // Number of results to return per page.

}

Reserved Fields

If you update a message type by entirely removing a field, or commenting it out, future users can reuse the tag number when making their own updates to the type. This can cause severe issues if they later load old versions of the same .proto, including data corruption, privacy bugs, and so on. One way to make sure this doesn't happen is to specify that the field tags (and/or names, which can also cause issues for JSON serialization) of your deleted fields are reserved. The protocol buffer compiler will complain if any future users try to use these field identifiers.

message Foo {

  reserved 2, 15, 9 to 11;

  reserved "foo", "bar";

}

Note that you can't mix field names and tag numbers in the same reserved statement.

What's Generated From Your .proto?

When you run the protocol buffer compiler on a .proto, the compiler generates the code in your chosen language you'll need to work with the message types you've described in the file, including getting and setting field values, serializing your messages to an output stream, and parsing your messages from an input stream.

· For C++, the compiler generates a .h and .cc file from each .proto, with a class for each message type described in your file.

· For Java, the compiler generates a .java file with a class for each message type, as well as a specialBuilder classes for creating message class instances.

· Python is a little different – the Python compiler generates a module with a static descriptor of each message type in your .proto, which is then used with a metaclass to create the necessary Python data access class at runtime.

· For Go, the compiler generates a .pb.go file with a type for each message type in your file.

· For Ruby, the compiler generates a .rb file with a Ruby module containing your message types.

· For JavaNano, the compiler output is similar to Java but there are no Builder classes.

· For Objective-C, the compiler generates a pbobjc.h and pbobjc.m file from each .proto, with a class for each message type described in your file.

· For C#, the compiler generates a .cs file from each .proto, with a class for each message type described in your file.

You can find out more about using the APIs for each language by following the tutorial for your chosen language (proto3 versions coming soon). For even more API details, see the relevant API reference (proto3 versions also coming soon).

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!