找回密码
 立即注册
查看: 201|回复: 5

Protobuf 在数据交换上有什么长处?

[复制链接]
发表于 2024-7-15 17:46 | 显示全部楼层 |阅读模式
Protobuf 在数据交换上有什么长处?
发表于 2024-7-15 17:46 | 显示全部楼层
1、引言

搞即时通讯IM方面开发的程序员,在谈到通讯层实现时,必然会提到网络编程。那么计算机网络编程中的一个非常基本的问题:到底该怎样组织Client与server之间交互的数据呢?
本篇文章我们不讨论IM系统中的那些高端技术话题,我们回归到通讯的本质——也就是数据在网络中交互时的编解码原理,并由浅入深从底层理解Protobuf的编解码技术实现。


2、共识与协议

针对引言中引出的“到底该怎样组织Client与Server之间交互的数据呢?”。
这个问题可不像看上去那样简单,因为Client进程和Server进程运行在不同的机器上,这些机器可能运行在不同的处理器平台、可能运行在不同的操作系统、可能是由不同的编程语言编写的,Server要怎样才能识别出Client发送的是什么数据呢?
就像这样:


如上图所示,Client给Server发送了一段数据:
0101000100100001
Server怎么能知道该怎样“解读”这段数据呢?
显然:Client和Server在发送数据之前必须首先达成某种关于怎样解读数据的共识,这就是所谓的协议。
这里的协议可以是这样的:“将每8个比特为一个单位解释为无符号数字”。
如果协议是上面这样定义的:那么Server接收到这串二进制后就会将其解析为 81(01010001) 与 33(00100001)。
当然,这里的协议也可以是这样的:“将每8个比特为一个单位解释为ASCII字符”,那么Server接收到这串二进制后就将其解析为“Q!”。
可见:同样一串二进制在不同的“上下文/协议”下有完全不一样的解读,这也是为什么计算机明明只认知0和1但是却能处理非常复杂任务的根本原因,因为一切都可以编码为0和1,同样的我们也可以从0和1中解析出我们想要的信息,这就是所谓的编解码技术。
实际上不止0和1,我们也可以将信息编码为摩斯密码(Morse code)等,只不过计算机擅长处理0和1而已。


扯远了,回到本文的主题。
3、一个例子:远程过程调用(RPC)

作为程序员我们知道,Client以及Server之间不会简单传递一串数字以及字符这样简单,尤其在互联网大厂后端服务这种场景下。
当我们在电商App里搜索商品、打车App里呼叫出租车以及刷短视频时,每一次请求的背后在后端都涉及大量服务之间的交互。
就像这样:


完成一次客户端请求gateway这个服务要“调用”N多个下游服务,所谓“调用”是说A服务向B服务发送一段数据(请求),B服务接收到这段数据后执行相应的函数,并将结果返回给A服务。
只不过对于服务A来说并不想关心网络传输这样的底层细节,如果能像调用本地函数一样调用远程服务就好了,这就是所谓的RPC。
经典的实现方式是这样的:


RPC对上层提供和普通函数一样的接口,只不过在实现上封装了底层复杂的网络通信(当然也包括协议的定义,协议的解解码等)。RPC框架是当前互联网后端的基石之一,很多所谓互联网后端的职位无非就是在此基础之上堆业务逻辑。
本文我们不关心其中的细节,我们只关心在网络层Client是怎样对请求参数进行编码、Server怎样对请求参数进行解码的,也就是本文开头提出的问题。
4、信息的编解码

4.1纯文本的编解码对人类很友好

在思考怎样进行编解码之前,我们必须意识到:

  • 1)Client和Server可能是用不同语言编写的(你的编解码方案必须通用且不能和语言绑定);
  • 2)编解码方法的性能问题必须要考虑(尤其是对时间要求苛刻的服务)。
首先,我们最应该能想到的就是以纯文本的形式来表示。
纯文本从来都是一种非常有友好的信息载体。为什么?很简单,因为人类(我们)可以直接看懂。
就像这段:
{
"widget": {
  "window": {
   "title": "Sample Konfabulator Widget",
   "name": "main_window",
   "width": 500,
   "height": 500
  },
  "image": {
   "src": "Images/Sun.png",
   "name": "sun1",
   "hOffset": 250,
   "vOffset": 250,
  },
}
}
是不是一目了然:只要我们实现约定好文本的结构(也就是语法),那么Client和Server就能利用这种文本进行信息的编码以及解码,不管Client和Server是运行在x86还是ARM、是32位的还是64位的、运行在Linux上还是Windows上、是大端还是小端,都可以无障碍交流。
因此:在这里,文本的语法就是一种协议(如下图所示)。


顺便说一句:你都规定好了文本的语法,实际上就相当于发明了一种语言。
这里用来举例用的语言就是所谓的JSON,只不过JSON这种语言不是用来表示逻辑(代码)而是用来存储数据的。
JSON就是这个老头提出来的:


除了JSON,另一种利用文本存储数据的表示方法是XML。
来一段XML感受下:
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
相对JSON来说是不是就没那么容易看懂了,自从JSON出现后在Web领域就逐渐取代了XML。
当两段数据量很少的时候——就像浏览器和服务端的交互,JSON可以工作的非常好(如下图所示)。
这个场景就是这样:


在这里是JSON的天下。
4.2纯文本对计算机来说不够友好

在上小节中我们知道,JSON这类纯文本的编解码方式对于人类非常友好。
但对于后端服务之间的交互(或者具体如IM里Client和Server之间的交互)来说就不一样了,后端服务之间的RPC调用可能会传输大量数据,如果全部用纯文本的形式来表示数据那么不管是网络带宽还是性能可能都会差强人意。


在这种场景下,JSON并不是最好的选项,主要原因之一就在于性能以及数据的体积。
我们知道:文本表示对人类是最友好的,对机器来说则不是这样,对机器来说最好的还是01二进制。
那么有没有二进制的编码方法吗?答案是肯定的,这就是当前互联网后端中流行的Protobuf,Google公司开源项目。
那么Protobuf有什么神奇之处吗?
假设Client端想给Server端传输这样一段信息:“我有一个id,其值为43”。
那么在XML下是这样表示的:
<id>43</id>
数一数这这段数据占据了多少字节,很显然是11字节。
而如果用JSON来表示呢?
{"id":43}
数一数这段数据占据了多少字节,显然是9字节。
而如果用Protobuf来表示呢? 是这样的:
//消息定义
message Msg {
  optional int32 id= 1;
}

//实例化
Msg msg;
msg.set_id(43);
其中Msg的定义看上去比JSON和XML更加复杂了,但这些只是给人看的,这些还会被protbuf进一步处理。
最终被Protobuf编码为:
1082b
也就是0x08与0x2b,这占据了多少字节呢?答案是2字节。
从JSON的9字节到Protobuf的2字节,数据大小减少了4倍多。
数据量的减少意味着:

  • 1)更少的网络带宽;
  • 2)更快的解析速度。
那么,Protobuf是怎样做到这一点的呢?
5、Protobuf是怎样实现编解码的?

首先,我们来思考最简单的情况,正常情况下,我们该怎样表示数字。
你可能会想这还不简单,统一用固定长度,比如用64个比特(8字节)。
这种方法可行,但问题是不论一个数字有多小,比方2,那么用这种方法表示2也需要占据64个比特(8字节),如下所示。


明明只要一个字节就能表示而我们却用了8个,前面的全都是0,这也太奢侈太浪费了吧。
显然,在这里我们不能使用固定长度来表示数字,而需要使用变长方法来表示。
什么叫变长?意思是说如果数字本身比较大,那么其使用的比特位可以较多,但如果数字很小那么就应该使用较少的比特位来表示,这就叫变长,随机应变,不死板。
那怎样变长呢?
我们规定:对于每一个字节来说,第一个比特位如果是1那么表示接下来的一个比特依然要用来解释为一个数字,如果第一个比特为0,那么说明接下来的一个字节不是用来表示该数字的。
也就是说对于每个8个比特(1字节)来说,它的有效载荷是7个比特,第一个比特仅仅用来标记是否还应该把接下来的一个字节解析为数字。
根据这个规定,假设来了这样一串01二进制:
1010110000000010
根据规定,我们首先取出第一个字节,也就是:
10101100
此时我们发现第一个比特位是1,因此我们知道接下来的一个字节也属于该数字。
将当前字节的1去掉就是:
0101100
然后我们看下一个字节:
00000010
我们发现第一个bit为0,因此我们知道下一个字节不属于该数字了。
接下来我们将解析到的0101100(第一个字节去掉第一个比特位)以及第二个字节0000010(第二个字节去掉第一个比特位)翻转之后拼接到一起(这里之所以翻转是因为我们规定数字的高位在后)。
这个过程就是:
     1010110000000010
->  10101100 | 00000010 //解析得到两个字节
    _          _

->  0101100  |  0000010  //各自去掉最高位
->  0000010  |  0101100  //两个字节翻转顺序

    0000010  +  0101100
->  100101100           //拼接
最后我们得到了100101100,这一串二进制表示数字300。
这种数字的变长表示方法在Protobuf中被称之为varint
因此在这种表示方法下,如果数字较大,那么使用的比特就多,如果数字较小那么使用比特就少,聪明吧。
有的同学看到这里可能会问题,刚才讲解的方法只能表示无符号数字,那么有符号数字该怎么表示呢?比如-2该怎么表示?
7、Protobuf的有符号数表示

按照刚才变长编码的思想,-2147483646使用的比特位应该比-2要少。
然而我们知道在计算机世界中负数使用补码表示的,也就是说最高位(最左侧的比特位)一定是1,假设我们使用64位来表示数字,那么如果我们依然用补码来表示数字的话那么无论这个负数有多大还是多小都需要占据10个字节的空间。
为什么是10个字节呢?
不要忘了varint每个字节的有效负荷是7个比特,那么对于需要64位表示的数字来说就需要64/7向上取整也就是10个字节来表示。
这显然不能满足我们对数字变长存储的要求。
该怎么解决这个问题呢?
既然无符号数字可以方便的进行变长编码,那么我们将有符号数字映射称为无符号数字不就可以了,这就是所谓的ZigZag编码,是不是很聪明。
ZigZag编码就像这样:
原始信息      编码后
0            0
-1           1
1            2
-2           3
2            4
-3           5
3            6

...          ...

2147483647   4294967294
-2147483648  4294967295
这样我们就可以将有符号数字转为无符号数字,接收方接收到该数据后再恢复出有符号数字。
现在数字的问题彻底解决了,但这仅仅是万里长征第一步。
8、Protobuf的字段名称与字段类型

对于任何一个有用的信息都包含这样几部分:

  • 1)字段名称;
  • 2)字段类型;
  • 3)字段值。
就像C/C++中定义变量时:
int i = 100;
在这里,字段名称就是i,字段类型是int,字段值是100。
刚才我们用varint以及ZigZag编码解决了字段值表示的问题,那么该怎样表示字段名称和字段类型呢?
首先,对于字段类型还比较简单,因为字段类型就那么多。
Protobuf中定义了6种字段类型:


对于6种字段类型我们使用3个比特位来表示就足够了。
接下来比较有趣的是字段名称该怎么表示呢?
假设我们需要传递这样一个字段:
int long_long_name = 100;
那么我们真的需要把“long_long_name”这么多字符通过网络传递给对端吗?
既然通信双方需要协议,那么“long_long_name”这字段其实是Client和Server都知道的,它们唯一不知道的就是“哪些值属于哪些字段”。
为解决这个问题,我们给每个字段都进行编号,比如通信双方都知道“long_long_name”这个字段的编号是2。那么对于“int long_long_name = 100; ”我们该怎么表示呢。
这个信息我们只需要传递:

  • 1)字段名称:2 (2对应字段“long_long_name”);
  • 2)字段类型:0 (0表示varint类型,参见上图);
  • 3)字段值:100。
所以我们可以看到,无论你用多么复杂的字段名称也不会影响编码后占据的空间,字段名称根本就不会出现在编码后的信息中,so clever。
9、从宏观上看Protobuf的编码原理

我们已经在Protobuf中看到了数字以及字段名称以及字段类型是怎么表示了,现在是时候从宏观角度来看看多个字段该怎么编码了。
从本质上讲,Protobuf被编码后形成一系列的key-value,每个key-value对应一个proto中的字段。
也就是键值对:


其中value比较简单,也就是字段值;而字段名称和字段类型会被拼接成key。Protobuf中共有6种类型,因此只需要3个比特位即可。字段名称只需要存储对应的编号。
这样就可以这样编码:
(字段编号 << 3) | 字段类型
假设Server接收到了一个key为0x08,其二进制的表示为:
0000 1000
由于key也是利用varint编码的,因此需要将第一个比特位去掉。
这样我的得到:
000 1000
根据key的编码方式,其后三个比特位表示字段类型,即:
000
也就是0,这样我们知道该key的类型是Varint(第0号类型),而字段编号为抹掉后3个比特位的值,即:
0001
这样,我们就知道了该key对应的字段编号为1,得到编号我们就能根据编号找到对应的编号名称。
10、Protobuf的嵌套数据

与JSON和XML类似,Protobuf中也支持嵌套消息.
就像这样:
message SubMsg {
  optional int32 id= 1;
}
message Msg {
  optional SubMsg msg = 1;
}
其实现也比较简单,这依然遵循被编码后形成一系列的key-value,只不过对于嵌套类型的key来说,其value是由子消息的key-value组成,如下图所示。


11、Protobuf与编译语言

与JSON一样,Protobuf也是一门语言,兼具了文本的可读性以及二进制的高效。
Protobuf之所以能做到这一点,就好比C语言与机器指令。
C语言是给程序员看的,可读性好。而机器指令是给硬件使用的,性能好。编译器会将C语言程序转为机器可执行的机器指令。
而Protobuf也一样,Protobuf也是一门语言,会将可读性较好的消息编码为二进制从而可以在网络中进行传播,而对端也可以将其解码回来。
在这里Protobuf中定义的消息就好比C语言,编码后的二进制消息就好比机器指令。
而Protobuf作为事实上语言必然有自己的语法。
其语法就是这样:


怎么样,还觉得编译原理没什么用吗?
不理解编译原理是不可能发明Protobuf这种技术的。
12、本文小结

我在写这篇文章时不断感叹,Google的这项技术节省了多少程序员的时间,同时我们也能看到这种基石般的技术依赖的底层原理却非常古老。
比如下面这些:

  • 1)信息的编解码;
  • 2)编译原理。
怎么样,这些是不是远远没有IT界各种流行的技术听上去时髦有趣,而正是这种朴素的技术支撑起了工业界,现在你也应该能明白底层技术的重要性了吧。
13、参考资料

[1]Protobuf官方网站
[2]Protobuf从入门到精通,一篇就够!
[3]如何选择即时通讯应用的数据传输格式
[4]强列建议将Protobuf作为你的即时通讯应用数据传输格式
[5]APP与后台通信数据格式的演进:从文本协议到二进制协议
[6]面试必考,史上最通俗大小端字节序详解
[7]移动端IM开发需要面对的技术问题(含通信协议选择)
[8]简述移动端IM开发的那些坑:架构设计、通信协议和客户端
[9]理论联系实际:一套典型的IM通信协议设计详解
[10]58到家实时消息系统的协议设计等技术实践分享
(本文引用自:http://www.52im.net/thread-4088-1-1.html)

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2024-7-15 17:46 | 显示全部楼层
一、protobuf是什么?

        protobuf(Google Protocol Buffers)是Google提供一个具有高效的协议数据交换格式工具库(类似Json),但相比于Json,Protobuf有更高的转化效率,时间效率和空间效率都是JSON的3-5倍。后面将会有简单的demo对于这两种格式的数据转化效率的对比。但这个库目前使用还不是太流行,据说谷歌内部很多产品都有使用。
二、protobuf有什么?

        Protobuf 提供了C++、java、python语言的支持,提供了windows(proto.exe)和linux平台动态编译生成proto文件对应的源文件。proto文件定义了协议数据中的实体结构(message ,field)
关键字message: 代表了实体结构,由多个消息字段(field)组成。
消息字段(field): 包括数据类型、字段名、字段规则、字段唯一标识、默认值
数据类型:常见的原子类型都支持(在FieldDescriptor::kTypeToName中有定义)
字段规则:(在FieldDescriptor::kLabelToName中定义)
        required:必须初始化字段,如果没有赋值,在数据序列化时会抛出异常
        optional:可选字段,可以不必初始化。
        repeated:数据可以重复(相当于java 中的Array或List)
        字段唯一标识:序列化和反序列化将会使用到。
默认值:在定义消息字段时可以给出默认值。
三、protobuf有什么用?

        Xml、Json是目前常用的数据交换格式,它们直接使用字段名称维护序列化后类实例中字段与数据之间的映射关系,一般用字符串的形式保存在序列化后的字节流中。消息和消息的定义相对独立,可读性较好。但序列化后的数据字节很大,序列化和反序列化的时间较长,数据传输效率不高。
        Protobuf和Xml、Json序列化的方式不同,采用了二进制字节的序列化方式,用字段索引和字段类型通过算法计算得到字段之前的关系映射,从而达到更高的时间效率和空间效率,特别适合对数据大小和传输速率比较敏感的场合使用。
四、Protobuf在Android上的使用

1、创建proto文件,定义消息的实体结构
2、编译proto文件生成对应的java文件
3、添加protobuf-java-2.5.0.jar到android工程
4、在android中实现对消息结构的序列化/反序列化  
五、Protobuf与json的对比

1、创建product.proto文件
        定义了三个Message(ProductInfo、PhoneInfo、Watch)消息结构
2、消息结构对应的java类(ProductInfo、PhoneInfo、Watch)
3、消息结构和java对象赋值
  1. PhoneName:” idol3”
  2. Price:2000
  3. Top:1
  4. WatchName:” tcl watch”
  5. Price:1000
  6. Top:1
复制代码
4、JSON字符串
  1. {"phone":{"phoneName":"idol3","price":2000,"top":1},"watch":{"watchName":"tcl wtch","top":1,"price":1000}}
复制代码
5、Protobuf转化后的二进制文件
空间效率
Json:107个字节
Protobuf:32个字节
时间效率
Json序列化: 1ms ,  反序列化:0ms
Protobuf 序列化: 0ms 反序列化:0ms
将public List<Phone> list和repeated PhoneInfo phoneInfoList =3;都赋值为1000个PhoneInfo
空间效率
Json:4206个字节
Protobuf:1332个字节
时间效率
Json序列化: 4ms ,  反序列化:1ms
Protobuf 序列化: 1ms 反序列化:0ms
LinuxC++服务器开发视频:c/c++ linux服务器开发/后台架构师-点击观看
LinuxC/C++服务器开发/架构师 面试题、学习资料、教学视频和学习路线图(资料包括 C/C++,Linux,golang 技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg 等),免费分享有需要的可以自行添加学习交流群994289133

六、protobuf的简单分析

1、优缺点
优点:通过以上的时间效率和空间效率,可以看出protobuf的空间效率是JSON的2-5倍,时间效率要高,对于数据大小敏感,传输效率高的模块可以采用protobuf库
缺点:消息结构可读性不高,序列化后的字节序列为二进制序列不能简单的分析有效性;目前使用不广泛,只支持java,C++和Python;
2、数据序列化/反序列化
a、规则:
protobuf把消息结果message也是通过 key-value对来表示。只是其中的key是采取一定的算法计算出来的即通过每个message中每个字段(field index)和字段的数据类型进行运算得来的key = (index<<3)|type;
type类型的对应关系如下:


Value会根据数据类型的不同会有两种表现形式:
对于各种int,bool,enum类型,value就是Varint
对于string,bytes,message等等类型,value就是length+原始内容编码
Varints是一种紧凑表示数字的方法。它用一个或者多个字节表示一个数字,值越小的数字字节数越少。相对于传统的用4字节表示int32类型数字,Varints对于小于128的数值都可以用一个字节表示,大于128的数值会用更多的字节来表示,对于很大的数据则需要用5个字节来表示。
Varints算法描述: 每一个字节的最高位都是有特殊含义的,如果是1,则表示后续的字节也是该数字的一部分;如果是0,则结束
b、demo生成的的二进制文件反序列化。
第1个字节 (0A)
字段索引(index):         0A = 0001010  0A>>3 = 001 = 1
数据类型(type):           0A = 0001010&111  = 2 (String);
第2个字节 (0C)
字符串长度(length):      0E = 12;
字符串:                         0A 05 69 64 6F 6C 33 10 01 18 BD 0F
第3个字节 (0A)
因为字符串是来自phoneInfo属于嵌套类型
字段索引(index):         0A = 0001010  0A>>3 = 001 = 1
数据类型(type):           0A = 0001010&111  = 2 (String);
第4-9个字节(69 64 6F 6C 33)
字符串长度(length):    05 = 5
字符串:                       69 64 6F 6C 33 = idol3
第10个字节 (10)
字段索引(index):         10 = 00010000    10A>>3 = 0010 = 2
数据类型(type):           10 = 00010000&111  = 0 (Varints);
第11个字节  (01)
Varints:                          01 = 00001字节的最高位为0 整数结束
Value:                            1;
第12个字节(18)
字段索引(index):           18 = 00011000    18>> 00011 = 3
数据类型(type):           18 = 00011000&111  = 0 (Varints);
第13个字节(D0)
最高位为1,整数计算到下一个字节
第14个字节(0F)
最高位为0,整数计算结束
Value:为11111010000 =2000
C、反序列化结果

phoneinfo为
phoneName = “idol3”
top = 1
price = 2000;
同样的方法watchInfo为:
watchName = “tcl name”
top = 1
price=2000
3、时间效率

通过protobuf序列化/反序列化的过程可以得出:protobuf是通过算法生成二进制流,序列化与反序列化不需要解析相应的节点属性和多余的描述信息,所以序列化和反序列化时间效率较高。
4、空间效率

xml、json是用字段名称来确定类实例中字段之间的独立性,所以序列化后的数据多了很多描述信息,增加了序列化后的字节序列的容量。
Protobuf的序列化/反序列化过程可以得出:
protobuf是由字段索引(fieldIndex)与数据类型(type)计算(fieldIndex<<3|type)得出的key维护字段之间的映射且只占一个字节,所以相比json与xml文件,protobuf的序列化字节没有过多的key与描述符信息,所以占用空间要小很多。
七、Protobuf的源码分析

1、protobuf在java使用的序列化流程
java程序调用parserFrom(byte[] data)开始字节序列的反序列,Java程序通过调用编译生类GenerateMessage中的wirteTo()方法开始将序列化后的字节写入输出流中
GenerateMessage 继承AbstractMessage类,序列化最终在AbstractMesssage中完成,序列化的实现过程:
a、遍历对象中Message结构()
调用AbstractMessage类中的writeTo()方法
b、 序列化Message中每一个字段
调用CodeOutputStream类中的writeMessageSetExtension()方法
c、 对于Varints  Tag 的序列化流程:
调用CodeOutputStream类中的writeUInt32()方法
调用CodeOutputStream类中的WriteRawVarint32()方法
d、 对于非Varints Tag的序列化
调用CodeOutputStream类中的WriteTag()方法
具体的序列化实现都在CodedOutputStream中完成
2、java使用protobuf 的反序列化流程分析
java程序通过调用parserFrom(byte[] data)开始反序列化
具体在com.google.protobuf. AbstractParser类中实现
最后在com.google.protobuf.CodedInputStream类中完成反序列化
3、动态编译

以windows下用protoc.exe工具实现proto文件编译为例,protoc.exe是用C++实现。在控制台执行命令:
编译的流程:
检查proto的语法规则
将proto的文件中的message结构转换为GenerateMessage类的子类,并实现Builder接口。
编译流程
http://Main.cc中的main()方法
http://Command_line_interface.cc中的Run()方法
Import类中Import()
在Descriptor中完成message消息的收集和转化。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2024-7-15 17:47 | 显示全部楼层
除了运行时的性能足够优秀之外,其对于开发者的友好程度也值得一提。使用上首先你需要以他的格式定义好消息的结构,这里的语法和C非常接近,属于不用看文档照着示例就能撸一个出来的地步。然后运行一条命令,其他语言当中的源代码自动帮你生成好了,不要你们开发者手写一行代码。丢进自己的工程就能用,这还有什么好说的?
我用过它的C++ binding,其中每个字段都会生成好各种不同的所有权管理方式的设置代码,避免多余的拷贝,很符合cpp程序员的控制感,对于性能更有好处。
发表于 2024-7-15 17:48 | 显示全部楼层
1:序列化后体积相比Json和XML很小,适合网络传输
2:支持跨平台多语言
3:消息格式升级和兼容性还不错
4:序列化反序列化速度很快,快于Json的处理速速
发表于 2024-7-15 17:48 | 显示全部楼层
主要好处是:体积还算小巧;生成和解析效率都不错;使用非常简单。
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-23 17:52 , Processed in 0.105925 second(s), 28 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表