Home Tags Posts tagged with "序列化"

序列化

0 16

久仰 Dubbo 大名,正好公司需要使用,特此学习

 

一、学习方法 —> 带有目的去看官方文档

因为我本身了解过RPC框架的大体架构和原理(理论+简单的实现),零散的学习过分布式,微服务的概念,使用 Dubbo+zookeeper 实现了简单的远程调用,有一定的基础,因此现阶段最需要的是 系统的梳理 + 知识的整合。

官方文档是最全面(利于整合),最顶层(利于梳理)的。

因此,我建议在开发过程中,如果需要学习一门新的技术,先思考自己是否有一定的基础,如果有,那么在学习官方文档的时候,就应该带有两个目的去学习

  1. 系统的梳理 
  2. 知识的整合(缺少的则趁此补充)

这样下来,不仅学会了其使用方法,也夯实了所有知识,打通全身脉络。

 

二、开始分析

Apache Dubbo 是一款微服务开发框架,它提供了 RPC通信 与 微服务治理 两大关键能力。这

意味着,使用 Dubbo 开发的微服务,将具备相互之间的远程发现与通信能力, 同时利用 Dubbo 提供的丰富服务治理能力,可以实现诸如服务发现、负载均衡、流量调度等服务治理诉求。同时 Dubbo 是高度可扩展的,用户几乎可以在任意功能点去定制自己的实现,以改变框架的默认行为来满足自己的业务需求。Dubbo3 基于 Dubbo2 演进而来,在保持原有核心功能特性的同时, Dubbo3 在易用性、超大规模微服务实践、云原生基础设施适配、安全设计等几大方向上进行了全面升级。 以下文档都将基于 Dubbo3 展开。

上述为Dubbo3简介的第一段话,可分析得出:

(1)Dubbo服务于 微服务开发框架

1.什么是微服务架构(组件化,服务化,以关联的业务逻辑为边界)

微服务架构强调“业务需要彻底的组件化和服务化”,原有的单个业务系统会拆分为多个可以独立开发,涉及,运行的小应用,这些小应用之间通过服务完成交互和集成微服务架构将相关联的业务逻辑及数据放在一起形成独立的边界,其目的是能在不影响其他应用组件(微服务)的情况下更快地交付并推出市

举例:航班预定应用

将航班预订应用划分为预订航班、时间表查询、计算票价、分配座位、管理奖励、更新客户、调整库存七个微服务实施

举例:先享后付应用

将先享后付应用划分为信用审核,资格申请,商家接入,交易完成四个微服务实施。

2.总结

Dubbo服务于 微服务架构,通过Dubbo可以将应用业务彻底的组件化和服务化,通过组件化和服务化,各个微服务可以自行部署,设计,开发,使得应用更加灵活,开发更加敏捷,服务的可用性更高。(例如信用审核由A小组开发,资格申请由B小组开发,这样两个小组同时进行,可加快开发效率;业务的组件化增强服务可用性,例如商家接入组件的更新不会影响到用户的信用审核及资格申请;微服务的语言无关性使应用更加灵活,例如商家接入组件使用Java语言开发,完成交易组件可以使用追求效率的Go语言)

 

(2)Dubbo提供的两大关键能力—RPC通信 与 微服务治理

RPC通信

1.首先明确RPC通信是一种通信方式(进程间8种通信方式详解 – 云+社区 – 腾讯云 (tencent.com)进程间通信的几种方式 – 云+社区 – 腾讯云 (tencent.com)

通信方式包括:

  • 管道(pipe)
  • 消息队列(消息传递系统)
  • 信号量通信(消息传递系统)
  • 信号(消息传递系统)
  • 共享内存通信(共享存储器系统)
  • 套接字通信(C/S系统)
  • RPC(C/S系统)

2.RPC通信具体描述

RPC—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。

3.RPC通信所带来的问题(谁能用通俗的语言解释一下什么是 RPC 框架? – 知乎 (zhihu.com)

  • Call ID映射(即服务注册,发现)
  • 序列化和反序列化(序列化协议,如hessian,dubbo)
  • 网络传输(数据发送模型)

4.总结

进程间的通信方式有多种,而RPC通信是一种基于C/S系统的通信方式,RPC通信屏蔽应用层和传输层(可理解为RPC框架完成了这两层的功能,实现方式可以自定义),这种方式使得远程调用和本地调用一样简单,但与此同时,RPC通信带来一些问题,如服务注册,服务发现,序列化和网络传输等;

Dubbo则是RPC通信的一种实现,提供Client-Based的服务发现机制,通过部署额外的第三方注册中心组件来协调服务发现过程,如Nacos,Zookeeper等;它支持多种通信协议(如Dubbo3,Dubbo2)实现序列化+网络传输。

 

微服务治理

1.什么是服务治理?

因为某些原因,我们选择了微服务架构,而微服务架构的特点和实现决定了它有一些待解决的问题。我们的目的是使项目通过微服务架构运行起来,那么我们就必须解决这些问题。

可以用这个例子来思考:

微服务运行起来—>人是健康的

微服务待解决的问题—>人生病了(可能是咳嗽(服务不问题))…

服务治理—>给人治病,而且是对症下药

总结:服务治理就是为了完成项目在微服务架构下的部署和运行这个目的,而对该架构的问题的解决。

2.服务治理究竟要治的是什么?

让我们先放下微服务,像《微服务设计》那本书中说的一样,把自己想象成一个城市规划师,我们的目标不是治理微服务,而是要治理一个城市的交通,那么我们会怎么思考?

 

在进行城市交通规划之前首先要做的第一个事情是收集信息,要能够知道这个城市发生了什么,所以在各个路口需要安装采集探头,记录车来车往的信息。有了信息以后就需要对信息进行分析,那么就需要可视化的图形界面,能够一眼就看出什么地方出了问题,通往哪个工厂的路坏了。发现了问题就要解决问题了,限制一下拥堵路段的流量,把去往一个公园的车辆导向到另外一个类似的公园。最后,如果把城市作为一个国家来考虑,那么每个进入这个城市的车辆都需要进行检查,看看有没有携带违禁品,最后给这些不熟悉道路的外地车规划路线。通过上面这个思考的过程,我们发现要对一个城市进行治理的时候,第一要采集信息,然后要能够对采集的信息进行监控和分析,最后根据分析的结果采取对应的治理策略。另外从整体安全的角度考虑还需要一个守门人

守门人—>采集信息—>监控和分析—>治理

因此我们也用同样的思路来思考服务治理,网关就是整个整体的守门人,日志采集,追踪工具,服务注册发现都是用来采集信息的,然后需要监控平台来展现这些采集的信息,并进行监控和分析。最后根据分析的结果采取治理策略,有的服务快撑不住了要限流,有的服务坏了要熔断,并且还能够及时的调整这些服务的配置。

下面的脑图就从这四个方面构建了一个简易的服务治理体系:请求网关,信息采集,信息分析,治理策略总结:微服务治理可以简易的分成四个方面,分别是请求网关,信息采集,信息分析,治理策略

 三、参考

0 21

今天在编写项目的时候遇到一个问题,特此记录

 

首先我的客户端向服务器端发送消息,告知其我需要得到当前所有连接的客户端列表

当时序列化会出错,而将object换成具体的list即可返回了

 

也就是说,如果需要序列化某个对象(该对象其中包含非基本数据类型)的话,最好还是清晰的定义该非基本数据类型,定义成Object可能会出问题

今天在公司学习项目代码的时候看到了

private static final long serialVersionUID = 1L;
特此记录


1、首先谈谈为什么要序列化对象

– 把对象转换为字节序列的过程称为对象的序列化。
– 把字节序列恢复为对象的过程称为对象的反序列化。

对象的序列化主要有两种用途:
1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
2) 在网络上传送对象的字节序列。

在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。

当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象

2、为什么要使用SerialversionUID呢

简单看一下 Serializable接口的说明

If a serializable class does not explicitly declare a serialVersionUID,
then the serialization runtime will calculate a default
serialVersionUID value for that class based on various aspects of the class,
as described in the Java(TM) Object Serialization Specification.

如果用户没有自己声明一个serialVersionUID,接口会默认生成一个serialVersionUID
However, it is stronglyrecommended that all serializable classes explicitly declareserialVersionUID values, since the default serialVersionUID computation is highly sensitive to class details that may vary depending on compiler implementations, and can thus result in unexpectedInvalidClassExceptions during deserialization.
但是强烈建议用户自定义一个serialVersionUID,因为默认的serialVersinUID对于class的细节非常敏感反序列化时可能会导致InvalidClassException这个异常。

其实也可以这样理解,相当于快递的打包和拆包,里面的东西要保持一致,不能人为的去改变他,不然就交易不成功。
序列化与反序列化也是一样,而版本号的存在就是要是里面内容要是不一致,不然就报错。像一个防伪码一样。

0 13

一句话,该关键字修饰的变量(但该关键字不能修饰方法或者类)不参与持久化

 

同样的static 修饰的也不会被序列化

 

static和transient在序列化和反序列化时的区别

滑倒的红烧鱼 2019-06-03 00:59:30 905 收藏 6
分类专栏: java 文章标签: 序列化 static transient
版权
今天学到序列化和反序列化,对于static修饰的变量和transient修饰的变量在序列化和反序列化中的区别进行一点基于个人理解的小小总结,希望能帮到正在迷惑的人,如果有不对的地方,欢迎指正。

首先我们来说一下static修饰的变量。被static修饰的变量为类变量,其初始化是在类加载的时候就已经初始化赋值,当然也可以在构造方法中对其进行修改或重新赋值。static修饰的变量可以通过 类名.变量名 的形式进行调用,也可以通过 对象.变量名 进行调用,虽然两种方式都可以调用到static修饰的变量,但是推荐使用前者,这样也便于后续一些问题的理解。
接下来说一下transient。transient是序列化和反序列化时用于标记属性是否可序列化反序列化的“标记”,带有transient修饰的成员变量,在类中相当于一个(没有static修饰的)成员变量,对于该类的使用没有任何影响,只是在序列化的时候,带有transient标记的变量,无法被序列化和反序列化。
讲到这里,还要讲一下我个人对于序列化的一点形象化理解。序列化是将对象的信息保存在文件中,注意这里所说的是对象,而不是类,所以任何与类相关的变量、方法都不能被序列化(即保存在文件中),这是首先序列化是用来存储对象的(再次重申)。序列化,是将符合要求(非静态,非transient修饰的属性)的对象内容保存在文件中,如果有属性是transient修饰的,则对象中会有这个属性,但是其值为默认值(int类型默认值为0,String默认值为null…)
下面说一下两者在使用时的区别
1.如果在一个类中定义了一个使用static修饰的变量,则在序列化的时候,该变量不可以被序列化,也就是说,当我们对一个对象进行序列化的时候,系统识别到这个对象里的age属性是类变量,那么就不会被序列化。但是我们在实验的时候发现,反序列化得到的对象可以调用age,这又是为什么呢?这是因为jvm的原因,由于age是类变量,在创建对象之前,age属性就已经被加载好了,当对象调用age的时候,系统发现没有成员变量age,但是有个Person类的类变量age,而且系统发现你的对象,正是Person类的实例,那就让你调用吧。(此处有点像第一点中所说的对于static变量的调用方法)。而此时我们换一台电脑,或者重启后,再对文件进行反序列化,会发现age的值成了0,这是由于虽然age无法被序列化,但是age这个属性是存在的,既然无法被序列化,就没有值了,那就用int类型的系统默认初始值0。
2.transient修饰的变量跟普通的成员变量在使用上没有任何区别,但是在序列化的时候,一旦系统识别到你的age属性有transient修饰,则age就不可以被序列化,还是同上一点所讲的一样,不能被序列化,并不是这个属性不存在了,而是属性的值无法被保存起来,也就是该属性的值就是默认值,相当于在创建类定义属性的时候不赋值,就是默认值。
大家看到这里,应该对于transient和static的区别有了一定的了解,我在此再贴上三个例子,来帮助大家进行理解。

 

————————————————
版权声明:本文为CSDN博主「滑倒的红烧鱼」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_35672147/article/details/90745921

0 17
作者:愚公要移山
链接:https://zhuanlan.zhihu.com/p/78781763
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
序列化工具性能评测:

之前曾经写了两篇java的序列的机制,一种是默认的java序列化机制,这种方式效率太低。另外一种是谷歌的protobuf,但是这种我们还要写proto文件,并且我们还要使用工具来编译生成java文件,实在太麻烦。但是protostuff却不一样,能够很好的解决上面两者的问题。这篇文章就研究一下如何去使用,并对其进行一个简单的分析。

一、认识protostuff

其实protostuff也是有局限性的,比如说在序列化的文件在10M以下的时候,还是使用java自带的序列化机制比较好,但是文件比较大的时候还是protostuff好一点,这里的10M不是严格的界限。

protostuff也是谷歌的产品,它是基于protobuf发展而来的,相对于protobuf提供了更多的功能和更简易的用法。

废话不多说,直接看一下protoStuff是如何使用的吧。

二、代码实现

环境准备:添加依赖或者是jar

<dependency>
       <groupId>io.protostuff</groupId>
       <artifactId>protostuff-core</artifactId>
       <version>1.6.0</version>
</dependency>
<dependency>
      <groupId>io.protostuff</groupId>
      <artifactId>protostuff-runtime</artifactId>
      <version>1.6.0</version>
</dependency>

这里我使用了maven,直接添加依赖即可,如果你没有使用maven,在百度上搜索相应的jar包就好了。

1、定义要序列化的bean

首先是学生类

public class Student {
    private  String name;
    private  int    age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    //getter和setter方法
    //toString方法
}

然后是学校类

public class School {
    private  String schoolName;
    private List<Student> students;
    public School(String schoolName, List<Student> students) {
        this.schoolName = schoolName;
        this.students = students;
    }
    //getter和setter方法
    //toString方法
}

在这里我们真正要序列化的是School,但是为了使得例子更有说服力,于是就在School里面定义了Student。

2、protoStuff序列化工具类

public class ProtostuffUtils {
    //避免每次序列化都重新申请Buffer空间
    private static LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
    //缓存Schema
    private static Map<Class<?>, Schema<?>> schemaCache = new ConcurrentHashMap<Class<?>, Schema<?>>();
    //序列化方法,把指定对象序列化成字节数组
    @SuppressWarnings("unchecked")
    public static <T> byte[] serialize(T obj) {
        Class<T> clazz = (Class<T>) obj.getClass();
        Schema<T> schema = getSchema(clazz);
        byte[] data;
        try {
            data = ProtostuffIOUtil.toByteArray(obj, schema, buffer);
        } finally {
            buffer.clear();
        }
        return data;
    }
    //反序列化方法,将字节数组反序列化成指定Class类型
    public static <T> T deserialize(byte[] data, Class<T> clazz) {
        Schema<T> schema = getSchema(clazz);
        T obj = schema.newMessage();
        ProtostuffIOUtil.mergeFrom(data, obj, schema);
        return obj;
    }
    @SuppressWarnings("unchecked")
    private static <T> Schema<T> getSchema(Class<T> clazz) {
        Schema<T> schema = (Schema<T>) schemaCache.get(clazz);
        if (schema == null) {
            schema = RuntimeSchema.getSchema(clazz);
            if (schema == null) {
                schemaCache.put(clazz, schema);
            }
        }
        return schema;
    }
}

这其实就是一个工具类,这里面的代码可以不用更改,直接拿过来就可以使用了。不过在这里有必要对里面的一些字段方法等进行一个说明。

(1)字段LinkedBuffer

这个字段表示,申请一个内存空间用户缓存,LinkedBuffer.DEFAULT_BUFFER_SIZE表示申请了默认大小的空间512个字节,我们也可以使用MIN_BUFFER_SIZE,表示256个字节。

(2)字段schemaCache

这个字段表示缓存的Schema。那这个Schema是什么呢?就是一个组织结构,就好比是数据库中的表、视图等等这样的组织机构,在这里表示的就是序列化对象的结构。

(3)方法serialize

public static <T> byte[] serialize(T obj) {
        Class<T> clazz = (Class<T>) obj.getClass();
        Schema<T> schema = getSchema(clazz);
        byte[] data;
        try {
            data = ProtostuffIOUtil.toByteArray(obj, schema, buffer);
        } finally {
            buffer.clear();
        }
        return data;
}

它是序列化方法,里面的代码很容易理解,首先获得要序列化对象的类,然后为其分配一个缓存空间,其次获得这个类的Schema。最后一行代码ProtostuffIOUtil.toByteArray进行序列化。

(4)方法deserialize

//反序列化方法,将字节数组反序列化成指定Class类型
    public static <T> T deserialize(byte[] data, Class<T> clazz) {
        Schema<T> schema = getSchema(clazz);
        T obj = schema.newMessage();
        ProtostuffIOUtil.mergeFrom(data, obj, schema);
        return obj;
}

表示反序列化,反序列里面的代码更简单了,首先根据序列化对象获取其组织结构Schema。然后根据byte直接mergeFrom成一个对象。

(5)方法getSchema

private static <T> Schema<T> getSchema(Class<T> clazz) {
        Schema<T> schema = (Schema<T>) schemaCache.get(clazz);
        if (schema == null) {
            schema = RuntimeSchema.getSchema(clazz);
            if (schema == null) {
                schemaCache.put(clazz, schema);
            }
        }
        return schema;
}

获取序列化对象的组织结构。

3、测试

public class Test {
    public static void main(String[] args) {
        Student stu1 = new Student("张三",20);
        Student stu2 = new Student("李四",21);
        List<Student> students = new ArrayList<Student>();
        students.add(stu1);
        students.add(stu2);
        School school = new School("西工大",students);
        //首先是序列化
        byte[] bytes = ProtostuffUtils.serialize(school);
        System.out.println("序列化后: " + bytes.length);
        //然后是反序列化
        School group1 = ProtostuffUtils.deserialize(bytes,School.class);
        System.out.println("反序列化后: " + school.toString());
    }
}

运行一下就能出现结果,很简单。上面的ProtostuffUtils是一个工具类,你可以保留下来,复制粘贴到任何地方使用。下面的小结是对其底层原理的解析,如果你只是简单的使用,到此就OK了。如果想深入了解可以接着往下看

0 15

最近在写一个聊天室Demo 的时候了解到ProtoStuff这种序列化方式,特此查阅资料并记录

之前在网络通信和通用数据交换等应用场景中经常使用的技术是 JSON 或 XML,而在最近的开发中接触到了 Google 的 ProtoBuf。

 

在查阅相关资料学习 ProtoBuf 以及研读其源码之后,发现其在效率、兼容性等方面非常出色。在以后的项目技术选型中,尤其是网络通信、通用数据交换等场景应该会优先选择 ProtoBuf。

自己在学习 ProtoBuf 的过程中翻译了官方的主要文档,一来当然是在学习 ProtoBuf,二来是培养阅读英文文档的能力,三来是因为 Google 的文档?不存在的!

看完这些文档对 ProtoBuf 应该就有相当程度的了解了。

翻译文档见 [索引]文章索引,导航为翻译 – 技术 – ProtoBuf 官方文档。

但是官方文档更多的是作为查阅和权威参考,并不意味着看完官方文档就能立马理解其原理。

本文以及接下来的几篇文章会对 ProtoBuf 的编码、序列化、反序列化、反射等原理做一些详细介绍,同时也会尽量将这些原理表达的更为通俗易懂。

何为 ProtoBuf

我们先来看看官方文档给出的定义和描述:

protocol buffers 是一种语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于(数据)通信协议、数据存储等。

Protocol Buffers 是一种灵活,高效,自动化机制的结构数据序列化方法-可类比 XML,但是比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单。

你可以定义数据的结构,然后使用特殊生成的源代码轻松的在各种数据流中使用各种语言进行编写和读取结构数据。你甚至可以更新数据结构,而不破坏由旧数据结构编译的已部署程序。

简单来讲, ProtoBuf 是结构数据序列化[1] 方法,可简单类比于 XML[2],其具有以下特点:

  • 语言无关、平台无关。即 ProtoBuf 支持 Java、C++、Python 等多种语言,支持多个平台
  • 高效。即比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单
  • 扩展性、兼容性好。你可以更新数据结构,而不影响和破坏原有的旧程序

序列化[1]:将结构数据对象转换成能够被存储和传输(例如网络传输)的格式,同时应当要保证这个序列化结果在之后(可能在另一个计算环境中)能够被重建回原来的结构数据或对象。
更为详尽的介绍可参阅 维基百科
类比于 XML[2]:这里主要指在数据通信和数据存储应用场景中序列化方面的类比,但个人认为 XML 作为一种扩展标记语言和 ProtoBuf 还是有着本质区别的。

使用 ProtoBuf

对 ProtoBuf 的基本概念有了一定了解之后,我们来看看具体该如何使用 ProtoBuf。
第一步,创建 .proto 文件,定义数据结构,如下例1所示:

 

// 例1: 在 xxx.proto 文件中定义 Example1 message
message Example1 {
    optional string stringVal = 1;
    optional bytes bytesVal = 2;
    message EmbeddedMessage {
        int32 int32Val = 1;
        string stringVal = 2;
    }
    optional EmbeddedMessage embeddedExample1 = 3;
    repeated int32 repeatedInt32Val = 4;
    repeated string repeatedStringVal = 5;
}

我们在上例中定义了一个名为 Example1 的 消息,语法很简单,message 关键字后跟上消息名称:

 

message xxx {

}

之后我们在其中定义了 message 具有的字段,形式为:

 

message xxx {
  // 字段规则:required -> 字段只能也必须出现 1 次
  // 字段规则:optional -> 字段可出现 0 次或1次
  // 字段规则:repeated -> 字段可出现任意多次(包括 0)
  // 类型:int32、int64、sint32、sint64、string、32-bit ....
  // 字段编号:0 ~ 536870911(除去 19000 到 19999 之间的数字)
  字段规则 类型 名称 = 字段编号;
}

在上例中,我们定义了:

  • 类型 string,名为 stringVal 的 optional 可选字段,字段编号为 1,此字段可出现 0 或 1 次
  • 类型 bytes,名为 bytesVal 的 optional 可选字段,字段编号为 2,此字段可出现 0 或 1 次
  • 类型 EmbeddedMessage(自定义的内嵌 message 类型),名为 embeddedExample1 的 optional 可选字段,字段编号为 3,此字段可出现 0 或 1 次
  • 类型 int32,名为 repeatedInt32Val 的 repeated 可重复字段,字段编号为 4,此字段可出现 任意多次(包括 0)
  • 类型 string,名为 repeatedStringVal 的 repeated 可重复字段,字段编号为 5,此字段可出现 任意多次(包括 0)

关于 proto2 定义 message 消息的更多语法细节,例如具有支持哪些类型,字段编号分配、import
导入定义,reserved 保留字段等知识请参阅 [翻译] ProtoBuf 官方文档(二)- 语法指引(proto2)

关于定义时的一些规范请参阅 [翻译] ProtoBuf 官方文档(四)- 规范指引

第二步,protoc 编译 .proto 文件生成读写接口

我们在 .proto 文件中定义了数据结构,这些数据结构是面向开发者和业务程序的,并不面向存储和传输。

当需要把这些数据进行存储或传输时,就需要将这些结构数据进行序列化、反序列化以及读写。那么如何实现呢?不用担心, ProtoBuf 将会为我们提供相应的接口代码。如何提供?答案就是通过 protoc 这个编译器。

可通过如下命令生成相应的接口代码:

 

// $SRC_DIR: .proto 所在的源目录
// --cpp_out: 生成 c++ 代码
// $DST_DIR: 生成代码的目标目录
// xxx.proto: 要针对哪个 proto 文件生成接口代码

protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/xxx.proto

最终生成的代码将提供类似如下的接口:

例子-序列化和解析接口.png
例子-protoc 生成接口.png

第三步,调用接口实现序列化、反序列化以及读写
针对第一步中例1定义的 message,我们可以调用第二步中生成的接口,实现测试代码如下:

 

//
// Created by yue on 18-7-21.
//
#include <iostream>
#include <fstream>
#include <string>
#include "single_length_delimited_all.pb.h"

int main() {
    Example1 example1;
    example1.set_stringval("hello,world");
    example1.set_bytesval("are you ok?");

    Example1_EmbeddedMessage *embeddedExample2 = new Example1_EmbeddedMessage();

    embeddedExample2->set_int32val(1);
    embeddedExample2->set_stringval("embeddedInfo");
    example1.set_allocated_embeddedexample1(embeddedExample2);

    example1.add_repeatedint32val(2);
    example1.add_repeatedint32val(3);
    example1.add_repeatedstringval("repeated1");
    example1.add_repeatedstringval("repeated2");

    std::string filename = "single_length_delimited_all_example1_val_result";
    std::fstream output(filename, std::ios::out | std::ios::trunc | std::ios::binary);
    if (!example1.SerializeToOstream(&output)) {
        std::cerr << "Failed to write example1." << std::endl;
        exit(-1);
    }

    return 0;
}

关于 protoc 的使用以及接口调用的更多信息可参阅 [翻译] ProtoBuf 官方文档(九)- (C++开发)教程

关于例1的完整代码请参阅 源码:protobuf 例1。其中的 single_length_delimited_all.* 为例子相关代码和文件。

因为此系列文章重点在于深入 ProtoBuf 的编码、序列化、反射等原理,关于 ProtoBuf 的语法、使用等只做简单介绍,更为详见的使用教程可参阅我翻译的系列官方文档。

关于 ProtoBuf 的一些思考

官方文档以及网上很多文章提到 ProtoBuf 可类比 XML 或 JSON。

那么 ProtoBuf 是否就等同于 XML 和 JSON 呢,它们是否具有完全相同的应用场景呢?

个人认为如果要将 ProtoBuf、XML、JSON 三者放到一起去比较,应该区分两个维度。一个是数据结构化,一个是数据序列化。这里的数据结构化主要面向开发或业务层面,数据序列化面向通信或存储层面,当然数据序列化也需要“结构”和“格式”,所以这两者之间的区别主要在于面向领域和场景不同,一般要求和侧重点也会有所不同。数据结构化侧重人类可读性甚至有时会强调语义表达能力,而数据序列化侧重效率和压缩。

从这两个维度,我们可以做出下面的一些思考。

XML 作为一种扩展标记语言,JSON 作为源于 JS 的数据格式,都具有数据结构化的能力。

例如 XML 可以衍生出 HTML (虽然 HTML 早于 XML,但从概念上讲,HTML 只是预定义标签的 XML),HTML 的作用是标记和表达万维网中资源的结构,以便浏览器更好的展示万维网资源,同时也要尽可能保证其人类可读以便开发人员进行编辑,这就是面向业务或开发层面的数据结构化

再如 XML 还可衍生出 RDF/RDFS,进一步表达语义网中资源的关系和语义,同样它强调数据结构化的能力和人类可读。

关于 RDF/RDFS 和语义网的概念可查询相关资料了解,或参阅 2-Answer 系列-本体构建模块(一)3-Answer 系列-本体构建模块(二) ,文中有一些简单介绍。

JSON 也是同理,在很多场合更多的是体现了数据结构化的能力,例如作为交互接口的数据结构的表达。在 MongoDB 中采用 JSON 作为查询语句,也是在发挥其数据结构化的能力。

当然,JSON、XML 同样也可以直接被用来数据序列化,实际上很多时候它们也是这么被使用的,例如直接采用 JSON、XML 进行网络通信传输,此时 JSON、XML 就成了一种序列化格式,它发挥了数据序列化的能力。但是经常这么被使用,不代表这么做就是合理。实际将 JSON、XML 直接作用数据序列化通常并不是最优选择,因为它们在速度、效率、空间上并不是最优。换句话说它们更适合数据结构化而非数据序列化。

扯完 XML 和 JSON,我们来看看 ProtoBuf,同样的 ProtoBuf 也具有数据结构化的能力,其实也就是上面介绍的 message 定义。我们能够在 .proto 文件中,通过 message、import、内嵌 message 等语法来实现数据结构化,但是很容易能够看出,ProtoBuf 在数据结构化方面和 XML、JSON 相差较大,人类可读性较差,不适合上面提到的 XML、JSON 的一些应用场景。

但是如果从数据序列化的角度你会发现 ProtoBuf 有着明显的优势,效率、速度、空间几乎全面占优,看完后面的 ProtoBuf 编码的文章,你更会了解 ProtoBuf 是如何极尽所能的压榨每一寸空间和性能,而其中的编码原理正是 ProtoBuf 的关键所在,message 的表达能力并不是 ProtoBuf 最关键的重点。所以可以看出 ProtoBuf 重点侧重于数据序列化 而非 数据结构化

最终对这些个人思考做一些小小的总结:

  1. XML、JSON、ProtoBuf 都具有数据结构化数据序列化的能力
  2. XML、JSON 更注重数据结构化,关注人类可读性和语义表达能力。ProtoBuf 更注重数据序列化,关注效率、空间、速度,人类可读性差,语义表达能力不足(为保证极致的效率,会舍弃一部分元信息)
  3. ProtoBuf 的应用场景更为明确,XML、JSON 的应用场景更为丰富。

下一篇

作者:404_89_117_101
链接:https://www.jianshu.com/p/a24c88c0526a
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。