2015-08-13 16:33:38 wangchong_fly 阅读数 8582
  • Unity3D移动端实战经验分享

    主要是围绕资源加载效率的优化,文本文件加载,比如xml序列化读取,protobuf文件序列化,以及消息事件封装及应用,shader的优化及运用,移动端实时阴影的绘制。

    22501 人正在学习 去看看 姜雪伟

protobuf支持的数据类型不是很丰富

protobuf属于轻量级的,因此不能支持太多的数据类型,下面是protobuf支持的基本类型列表,一般都能满足需求,不过在选择方案之前,还是先看看是否都能支持,以免前功尽弃。同样该表也值得收藏,作为我们在定义类型时做参考。

.proto type

c++

notes

double

double

 

float

float

 

int32

int32

使用可变长编码方式,负数时不够高效,应该使用sint32

int64

int64

同上

uint32

uint32

使用可变长编码方式

uint64

uint64

同上

sint32

int32

使用可变长编码方式,有符号的整型值,编码时比通常的int32高效

sint64

sint64

同上

fixed32

uint32

总是4个字节,如果数值总是比2^28大的话,这个类型会比uint32高效

fixed64

uint64

总是8个字节,如果数值总是比2^56大的话,这个类型会比uint64高效

sfixed32

int32

总是4个字节

sfixed64

int64

总是8个字节

bool

bool

 

string

string

一个字符串必须是utf-8编码或者7-bitascii编码的文本

bytes

string

可能包含任意顺序的字节数据


2018-04-10 16:28:07 Dawn_sf 阅读数 1001
  • Unity3D移动端实战经验分享

    主要是围绕资源加载效率的优化,文本文件加载,比如xml序列化读取,protobuf文件序列化,以及消息事件封装及应用,shader的优化及运用,移动端实时阴影的绘制。

    22501 人正在学习 去看看 姜雪伟

浅析Protobuf数据格式





Protobuf是Google开源的一款类似于Json,XML数据交换格式,其内部数据是纯二进制格式,不依赖于语言和平台,具有简单,数据

量小,快速等优点. 目前用于序列化于反序列化官方支持的语言有C++,C#,JAVA,PYTHON. 适用于大小在1M以内的数据,因为像

在移动设备平台,内存是非常珍贵的.

使用的方法也比较简单:

1.定义用于消息文件.proto

2.使用protobuf的编译器编译消息文件

3.使用编译好对应语言的类文件进行消息的序列化于反序列化


Protobuf消息定义


消息由至少一个字段组合而成,类似于C语言中的结构,每个字段都有一定的格式.

字段格式:限定修饰符 | 数据类型 | 字段名称 = | 字段编码值 | [字段默认值]

1.限定修饰符包括 required\optional\repeated

Required:表示是一个必须字段,必须相对于发送方,在发送消息之前必须设置该字段的值,对于接收方,必须能够识别该字段

意思.发送之前没有设置required字段或者无法识别required字段都会引发编解码异常,导致消息被丢弃.

Optional:表示是一个可选字段,可选对于发送方,在发送消息时可以有选择性的设置或者不设置该字段的值. 对于接收方,如

能够识别可选字段就进行相应处理,如果无法识别,则忽略该字段,消息中的其他字段正常处理. --因为optional字段的特性

,很多接口在升级版本中都把后来添加的字段都统一的设置为optional字段,这样老的版本无需升级程序也可以正常通信,只不

过不能享受补丁内容罢了.

Repeated:表示该字段可以包含0~N个元素. 其特性和optional一样,但是每一次可以包含多个值. 可以看作是在传递一个数组的值.

2.数据类型

Protobuf定义了一套基本数据类型,几乎都可以映射到C++/JAVA等语言的剧本


3.字段名称

字段名称的命名与C/C++,java等语言的变量命名方式几乎相同的.

protobuf建议字段的命名采用以下划线分割的驼峰式first_name, 而不是firstName.

4.字段编码值

有了该值,通信商法才能相互识别对方的字段,当然相同的编码值,其限定修饰符和数据类型必须相同.

编码值的取值范围为1~2^32,其中1~15的编码时间和空间效率都是最高的,编码值越大,其编码的时间和空间效率就越低,当然

一般情况下相邻的2个值编码效率是相同的,除非两个值恰好是在4字节,12字节的临界区,比如15和16.

5.默认值

当在传输数据的时候,对于required数据类型,如果用户没有设置值,则使用默认值传递到对端. 当接收数据是对于optional字

段,如果没有接收到optional字段,则设置为默认值.

关于import protobuf接口文件可以像C语言的h文件一个,分离为多个,再需要的时候通过import导入需要对文件. 其行为和C语

言的#include大致相同.


关于package

避免名称冲突,可以给每一个文件指定一个package名称,对于C++则解析为名称空间.

关于message

支持嵌套消息,消息可以包含另一个消息作为其字段,也可以在消息内定义一个新的消息.

关于enum

枚举的定义和C++相同,但是有一些限制. 枚举值必须大于等于0的整数. 使用分号(;)分隔枚举变量而不是C++语言中的逗号(,)

protobuf
2019-11-15 20:02:01 weixin_45845578 阅读数 6
  • Unity3D移动端实战经验分享

    主要是围绕资源加载效率的优化,文本文件加载,比如xml序列化读取,protobuf文件序列化,以及消息事件封装及应用,shader的优化及运用,移动端实时阴影的绘制。

    22501 人正在学习 去看看 姜雪伟

Protocol buffers (简称Protobuf)是由谷歌开发的一种语言无关、平台无关、可扩展的序列化结构数据的方法,类似于XML,但是protobuf具备更加简便高效等优点。Protobuf支持的编程语言包括:Java、 C++、Python、 Java Lite、 Ruby、JavaScript、 Objective-C和C#,proto3还增加了对GO的支持。Protobuf广泛的应用于结构化数据传输和存储等方面。

2019-04-25 22:22:52 qq_41345773 阅读数 60
  • Unity3D移动端实战经验分享

    主要是围绕资源加载效率的优化,文本文件加载,比如xml序列化读取,protobuf文件序列化,以及消息事件封装及应用,shader的优化及运用,移动端实时阴影的绘制。

    22501 人正在学习 去看看 姜雪伟

使用Protobuf来编解码。Netty对Protobuf的支持比较好,还提供了Protobuf的编解码器,非常方便。

Protobuf介绍

GitHub地址:https://github.com/google/protobuf

Protobuf是google开源的项目,全称 Google Protocol Buffers,特点如下:

  • 支持跨平台多语言,支持目前绝大多数语言例如C++、C#、Java、pthyon等

  • 高性能,可靠性高,google出品有保障

  • 使用protobuf编译器能自动生成代码,但需要编写proto文件,需要一点学习成本

Protobuf使用

Protobuf是将类的定义使用.proto文件进行描述,然后通过protoc.exe编译器,根据.proto自动生成.java文件,然后将生成的.java文件拷贝到项目中使用即可。

在Github主页我们下周Windows下的编译器,可以在releases页面下载:https://github.com/google/protobuf/releases

protoc.exe编译器下载

下载完成之后放到磁盘上进行解压,可以将protoc.exe配置到环境变量中去,这样就可以直接在cmd命令行中使用protoc命令,也可以不用配置,直接到解压后的protoc\bin目录下进行文件的编译。

下面我们基于之前的Message对象来构建一个Message.proto文件。

syntax = "proto3";
option java_outer_classname = "MessageProto";
message Message {  
  string id = 1;
  string content = 2;
}

syntax 声明可以选择protobuf的编译器版本(v2和v3)

  • syntax="proto2";选择2版本

  • syntax="proto3";选择3版本

  1. option java_outer_classname="MessageProto"用来指定生成的java类的类名。

  2. message相当于c语言中的struct语句,表示定义一个信息,其实也就是类。

  3. message里面的信息就是我们要传输的字段了,子段后面需要有一个数字编号,从1开始递增

  4. .proto文件定好之后就可以用编译器进行编译,输出我们要使用的Java类,我们这边不配置环境变量,直接到解压包的bin目录下进行操作

首先将我们的Message.proto文件复制到bin目录下,然后在这个目录下打开CMD窗口,输入下面的命令进行编译操作:

protoc ./Message.proto --java_out=./

--java_out是输出目录,我们就输出到当前目录下,执行完之后可以看到bin目录下多了一个MessageProto.java文件,把这个文件复制到项目中使用即可。

Nettty整合Protobuf

首先加入Protobuf的Maven依赖:

<!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java -->
<dependency>
   <groupId>com.google.protobuf</groupId>
   <artifactId>protobuf-java</artifactId>
   <version>3.5.1</version>
</dependency>

创建一个Proto的Server数据处理类,之前的已经不能用了,因为现在传输的对象是MessageProto这个对象。

public class ServerPoHandlerProto extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        MessageProto.Message message = (MessageProto.Message) msg;
        if (ConnectionPool.getChannel(message.getId()) == null) {
            ConnectionPool.putChannel(message.getId(), ctx);
        }
        System.err.println("server:" + message.getId());
        ctx.writeAndFlush(message);
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

改造服务端启动代码,增加protobuf编解码器,是Netty自带的,不用我们去自定义。

public class ImServer {
    public void run(int port) {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() { 
                    @Override
                    public void initChannel(SocketChannel ch) throws Exception {
                        // 实体类传输数据,protobuf序列化
                        ch.pipeline().addLast("decoder",  
                                new ProtobufDecoder(MessageProto.Message.getDefaultInstance()));  
                        ch.pipeline().addLast("encoder",  
                                new ProtobufEncoder());  
                        ch.pipeline().addLast(new ServerPoHandlerProto());
                    }
                })
                .option(ChannelOption.SO_BACKLOG, 128)
                .childOption(ChannelOption.SO_KEEPALIVE, true);
        try {
            ChannelFuture f = bootstrap.bind(port).sync();
             f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
}

服务端改造完了,下面需要把客户的的Handler和编解码器也改成protobuf的就行了,废话不多说,直接上代码:

public class ImConnection {
    private Channel channel;
    public Channel connect(String host, int port) {
        doConnect(host, port);
        return this.channel;
    }
    private void doConnect(String host, int port) {
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(workerGroup);
            b.channel(NioSocketChannel.class);
            b.option(ChannelOption.SO_KEEPALIVE, true);
            b.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) throws Exception {
                    // 实体类传输数据,protobuf序列化
                    ch.pipeline().addLast("decoder",  
                            new ProtobufDecoder(MessageProto.Message.getDefaultInstance()));  
                    ch.pipeline().addLast("encoder",  
                            new ProtobufEncoder());  
                    ch.pipeline().addLast(new ClientPoHandlerProto());
                }
            });
            ChannelFuture f = b.connect(host, port).sync();
            channel = f.channel();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}

客户的数据处理类:

public class ClientPoHandlerProto extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        MessageProto.Message message = (MessageProto.Message) msg;
        System.out.println("client:" + message.getContent());
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

最后一步就开始测试了,需要将客户的发送消息的地方改成MessageProto.Message对象,代码如下:

/**
 * IM 客户端启动入口
 * @author yinjihuan
 */
public class ImClientApp {
    public static void main(String[] args) {
        String host = "127.0.0.1";
        int port = 2222;
        Channel channel = new ImConnection().connect(host, port);
        String id = UUID.randomUUID().toString().replaceAll("-", "");
        // protobuf
        MessageProto.Message message = MessageProto.Message.newBuilder().setId(id).setContent("hello yinjihuan").build();
        channel.writeAndFlush(message);
    }
}

源码参考:https://github.com/yinjihuan/netty-im

2015-10-09 11:22:12 ashimidashajia 阅读数 887
  • Unity3D移动端实战经验分享

    主要是围绕资源加载效率的优化,文本文件加载,比如xml序列化读取,protobuf文件序列化,以及消息事件封装及应用,shader的优化及运用,移动端实时阴影的绘制。

    22501 人正在学习 去看看 姜雪伟

Protobuf是Google开发一种数据描述语言,能够将结构化数据序列化,可用于数据存储、通信协议等方面。据Google官方文档介绍,现在Google内部已经有48,162个消息类型定义在12,183个proto文件中。

简介

protobuf是Google开发的一种数据描述语言语言,能够将结构化的数据序列化,可用于数据存储,通信协议等方面,官方版本支持C++,Java,Python,社区版本支持更多语言。

比如说程序中生成了一个链表,但是程序退出重启后,还要重新生成链表,有时候,我们很需要上次程序中该链表中记录的数据。这些数据或许是经过很多大量运算生成的,每次都重新生成这些数据的话,需要消耗大量时间。这时候就可以考虑使用protobuf,将其序列化后保存在文件中,下次使用的时候,加载文件,反序列化后就可以直接使用了。

该项目在github的官方地址

值得注意的是,protobuf是以二进制来存储数据的。相对于JSON和XML具有以下优点:

  1. 简洁

  2. 体积小:消息大小只需要XML的1/10 ~ 1/3

  3. 速度快:解析速度比XML快20 ~ 100倍

  4. 使用Protobuf的编译器,可以生成更容易在编程中使用的数据访问代码

  5. 更好的兼容性,Protobuf设计的一个原则就是要能够很好的支持向下或向上兼容

protobuf开发环境

主要介绍c++,Java开发环境,使用的版本protobuf 2.3.0。因为Android5.1.1源码中使用的就是该版本,为了能快速编译出Android能使用的so库,所以选择这个版本。如果喜欢最新版,可以从上面的github官网下载。

点此下载protobuf 2.3.0版本源码

ubuntu :
./configuer --prefix=指定安装路径(可以不指定)

make 

make install (可能需要sudo权限)

会编译出一个protoc的可执行程序,以及开发所需的头文件和库。这个可执行程序,要经常使用,用来将proto协议文件,转换为头文件和对应的代码。

java:

点此进入jar包下载页面,选择2.3.0版本。供java程序调用。

Android native:

Android 源码/external/protobuf

直接:

mmm external/protobuf

会编译出j出很多jar,和c++静态库。jar,推荐还是选择从上面下载的,静态库使用不是很方便。这里修改Android.mk编译so库版本。

# C++ full library - libcxx version
# =======================================================
include $(CLEAR_VARS)

LOCAL_MODULE := libprotobuf-cpp-2.3.0-full-libcxx-rtti
LOCAL_MODULE_TAGS := optional
LOCAL_CPP_EXTENSION := .cc
LOCAL_SRC_FILES := $(protobuf_cc_full_src_files)
LOCAL_C_INCLUDES := \
    $(LOCAL_PATH)/android \
    external/zlib \
    $(LOCAL_PATH)/src

LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)

LOCAL_CFLAGS := -frtti $(IGNORED_WARNINGS)
LOCAL_CPPFLAGS := -w

LOCAL_SHARED_LIBRARIES := libz

include external/libcxx/libcxx.mk

include $(BUILD_SHARED_LIBRARY)
#include $(BUILD_STATIC_JAVA_LIBRARY)
# Clean temp vars
protobuf_cc_full_src_files :=

这里还用到了libcxx库,该库实现了c++11很多特性,开发Android Native程序时,我经常使用到这个库,所以这里也用该库来编译protobuf了。

成功后,如下所示:

Install: out/target/product/shamu/system/lib/libprotobuf-cpp-2.3.0-full-libcxx-rtti.so
快速使用

主要是介绍如何使用,前面在PC主机中编译出来的protoc可执行程序,将proto协议文件转换为对应的代码,也顺便看下proto协议文件长什么样子。

如下片段,摘自protobuf源码中的example中的addressbook.proto

message Person {
  required string name = 1;
  required int32 id = 2;        // Unique ID number for this person.
  optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phone = 4;
}

使用protoc命令生成代码,使用­­cpp_out、­­java_out、­­python_out命令选项可以生成C++、Java、Python代码。例如:

protoc addressbook.proto -I. --cpp_out=. 

上述命令执行后,产生两个用于c++新文件:

addressbook.pb.cc
addressbook.pb.h

可以将cc改为cpp.之后在自己的工程中包含这两个文件就可以按照下面所示使用proto中定义的数据协议了:

这两个文件可以大致粗略的看看,因为可以从中知道定义哪些可用的调用方法等,一看便知怎么用。

proto文件的语法

在消息定义中,需要明确以下三点:

  1. 确定消息命名,给消息取一个有意义的名字。

  2. 指定字段的类型

  3. 定义字段的编号,在Protocol Buffers中,字段的编号非常重要,字段名仅仅是作为参考和生成代码用。需要注意的是字段的编号区间范围,其中19000 ~ 19999被Protocol Buffers作为保留字段。

先来看一个非常简单的例子。假设你想定义一个“搜索请求”的消息格式,每一个请求含有一个查询字符串、你感兴趣的查询结果所在的页数,以及每一页多少条查询结果。可以采用如下的方式来定义消息类型的.proto文件了:

message SearchRequest {
  required string query = 1;
  optional int32 page_number = 2;
  optional int32 result_per_page = 3;
}

SearchRequest就是消息的名字,该消息有3个字段,在消息中承载的数据分别对应于每一个字段。其中每个字段都有一个修饰符,一种类型,一个名字和一个编号。

所指定的字段类型修饰符必须是如下之一:

required:一个格式良好的消息一定要含有1个这种字段。表示该值是必须要设置的;

optional:消息格式中该字段可以有0个或1个值(不超过1个),也就是可有可无;

repeated:在一个格式良好的消息中,这种字段可以重复任意多次(包括0次)。重复的值的顺序会被保留。表示该值可以重复,相当于java中的List。

其中数据类型和c++,java的对应如下:

分配编号:
正如上述文件格式,在消息定义中,每个字段都有唯一的一个数字标识符。这些标识符是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改变。注:[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识号。切记:要为将来有可能添加的、频繁出现的标识号预留一些标识号。

消息也是可以嵌套的,即message套message,还可以使用枚举。还可以使用import导入其他proto文件。

包(Package)

通常都要为.proto文件增加一个package声明符,用来防止不同的消息类型有命名冲突。

package foo.bar;
message Open { ... }

在其他的消息格式定义中可以使用包名+消息名的方式来定义域的类型,如:

message Foo {
  ...
  required foo.bar.Open open = 1;
  ...
}

包的声明符会根据使用语言的不同影响生成的代码。

对于C++,产生的类会被包装在C++的命名空间中,如上例中的Open会被封装在 foo::bar空间中;

对于Java,包声明符会变为java的一个包,除非在.proto文件中提供了一个明确有java_package;

对于 Python,这个包声明符是被忽略的,因为Python模块是按照其在文件系统中的位置进行组织的。

protobuf使用流程

  1. 编写协议文件,也就是.proto文件

  2. 利用protoc命令将协议文件转换为我们需要的开发语言接口.该接口中包含了对协议中定义数据的操作方法.可以从生成的.h文件中查看有哪些接口,常用的就是设置字段数据,获取字段数据,序列化,反序列化等;

    列举一些公用方法:

isInitialized(): 检查是否所有的required字段是否被赋值
toString(): 返回一个便于阅读的message表示(本来是二进制的,不可读)
byte[] toByteArray();: 序列化message并且返回一个原始字节类型的字节数组
static XXX parseFrom(byte[] data);: 将给定的字节数组解析为message
void writeTo(OutputStream output);: 将序列化后的message写入到输出流
static Person parseFrom(InputStream input);: 读入并且将输入流解析为一个message
  1. 在项目中使用生成的接口操作协议数据;

    协议中每个message都会转换为一个类,使用的时候创建一个消息对象,初始化里面的属性值并且序列化,然后可以存储到文件,可以socket发送等等.

    使用数据的时候,先反序列化,然后就可以正常使用了.

  2. c++编译如下,注意加pthread库,否则出错.

g++  xx.cpp xx.pb.cc  -I /usr/local/protobuf/include -L /usr/local/protobuf/lib -lprotobuf -pthread

protobuf编码协议

搞清楚编码协议,是为了能清楚数据大小,这样才调试socket程序的时候,能看懂数据长度的含义.

在Protobuf中采用Base­128变长编码,所谓变长编码是和定长编码相对的,定长编码使用固定字节数来表示,如int32类型的数字固定使用4 bytes表示,而变长编码是需要几个字节就使用几个字节,如对于int32类型的数字1来说,只需要1 bytes足够。Base­128变长编码的原则就两条

  1. 每个字节使用低7位表示数字,除了最后一个字节,其他字节的最高位都设置为1。

  2. 采用Little­Endian字节序

test.proto

message Book{
    required uint32 money = 1;
}

编译proto文件:

protoc test.proto -I. --cpp_out=.

main.cpp

#include "test.pb.h"

#include <stdio.h>

int main(){

    Book book;
    book.set_money(150);
    int size = book.ByteSize();
    unsigned  char bts[size];
    book.SerializeToArray(bts, size);
    for(int i=0;i<size;i++)
        printf("0x%x \n",bts[i]);
    return 0;
}
g++ main.cpp test.pb.cpp -I ~/mylib/protobuf/include -L ~/mylib/protobuf/lib -lprotobuf -o test -lpthread

运行test:

0x8 
0x96 
0x1 

一个Protobuf的消息包含一系列字段key/value,每个字段由一个变长32位整数作为字段头,后面跟随字段体。字段头,也就是key的格式如下:

(field_number << 3) | wire_type
­field_number:   字段序号 
­wire_type:  字段编码类型

字段编码类型如下:

字段序号,就是在定义message中的字段的时候添加的标号.

再看一个例子:

看嵌套:

message Test1{
requaried int32 a = 1;
}
message Test3{
requaried Test1 c = 3;
}

负数编码:

采用ZigZag Encoding

Protobuf入门

阅读数 34

Google ProtoBuf用法

阅读数 349

没有更多推荐了,返回首页