通讯协议序列化解读(一) Protobuf详解教程

前言:说到JSON可能大家很熟悉,是目前应用最广泛的一种序列化格式,它使用起来简单方便,而且拥有超高的可读性。但是在越来越多的应用场景里,JSON冗长的缺点导致它并不是一种最优的选择。

一、常用序列化格式介绍

目前JAVA常用的序列化有protobuf,json,xml,Serializable,hessian,kryo。他们的优缺点如下:

  • JSON:不多说了,用途广泛,序列化方式还衍生了阿里的fastjson,美团的MSON,谷歌的GSON等更加优秀的转码工具。
    优点:使用方便。
    缺点:数据冗长,转码性能一般。

  • XML:很久之前的转码方法了,现在用的不多。
    优点:暂时没发现。
    缺点:数据冗长,转码性能一般。

  • Serialzable:JDK自带的序列化。
    优点:使用方便。
    缺点:转码性能低下。

  • hessian:基于 binary-RPC实现的远程通讯library,使用二进制传输数据。
    优点:数据长度小。
    缺点:性能低下。

说了这么多,全是性能低下,MMP一群智障儿?当然不是!kryo就是一款快速、高效的序列化框架,但是它不是我们今天的主角,因为他只能在java中使用,和前端非java语言的通讯就存在极大的隔阂。我们今天的主角是protobuf?emmm,算是吧,但是也不全是,先给大家说下protobuf吧。

  • protobuf:谷歌公司出的一款开源项目,性能好,效率高,并且支持多种语言,例如:java,C++,python等。

    优点:转码性能高,支持多语言。
    缺点:中文文档少,使用相对复杂。




二、protobuf详解

在使用protobuf之前,需要安装protobuf编译器和运行时环境。
由于protobuf是跨平台,跨语言的,所以需要下载和安装对应版本的编译器和运行时依赖。

2.1 proto语法介绍


.proto Type说明C++ TypeJava TypePython Type[2]Go Type
double   double double float float64
float   float float float float32
int32 使用可变长度编码。对负数进行编码时比较低效 – 如果你的字段要使用负数值,请使用sint32来代替。 int32 int int int
int64 使用可变长度编码。对负数进行编码时比较低效 – 如果你的字段要使用负数值,请使用sint64来代替。 int64 long int/long[3] int64
uint32 使用可变长度编码 uint32 int[1] int/long[3] uint32
uint64 使用可变长度编码 uint64 long[1] int/long[3] uint64

 

详细语法由于篇章太多不在此做介绍,详情点开另一篇博文:http://www.cnblogs.com/tohxyblog/p/8974763.html

 

2.2使用教程

2.2.1导包
1
2
3
4
5
6
  <!-- protobuf-谷歌 -->
  <dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>3.5.1</version>
</dependency>

  

2.2.1下载编译器编译文件

下载地址:https://github.com/google/protobuf/releases
选择对应系统的版本,下载后解压。
可以通过定义好的.proto文件来生成Java代码,需要基于.proto文件运行protocol buffer编译器protoc。如果你没有安装编译器,下载安装包并遵照README安装。
通过如下方式调用protocol编译器:

1
protoc -I=/Users/rinzz04/Desktop/proto/proto --java_out=/Users/rinzz04/Desktop/proto/ /Users/rinzz04/Desktop/proto/proto/InitGame.proto

  

  • -I=proto文件存放路径
  • --java_out=生成的java文件夹目录
  • 后面紧跟proto具体文件

proto文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
syntax = "proto3";
 
message User {
    string userId = 1;
 
    string userName = 2;
 
    bool sex = 3;
 
    string  openId = 4;
 
    string  createTime = 5;
 
    string phoneNum = 6;
 
    string userImg = 7;
 
    string introduct = 8;
}

  

2.2.3protobuf使用教程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//以user为例编码成byte[]
UserOuterClass.User.Builder userBuild = UserOuterClass.User.newBuilder();
userBuild.setUserId(user.getUserId());
userBuild.setUserName(user.getUserName());
userBuild.setPhoneNum(user.getPhoneNum());
userBuild.setCreateTime(user.getCreateTime());
userBuild.setOpenId(user.getOpenId());
userBuild.setIntroduct(user.getIntroduct());
userBuild.setSex(user.isSex());
userBuild.setUserImg(user.getUserImg());
userBuild .toByteArray();//得到byte[]
 
 
//以user为例解码
UserOuterClass.User.Builder userBuild = UserOuterClass.User.newBuilder();
User user= user.build();
user=User.parseFrom(data.getValue().getBytes());

  

三、protobuf在实际操作中存在的问题

protobuf主要用于与前端通信编解码,那么在后台收到二进制如何存入到数据库中呢,或者说从数据库中取得的数据怎么映射到protobean呢。
由于protoc生成的java文件与我们平时写的java文件有区别,但是实际上都是有getset方法,不怕麻烦的童鞋可以直接通过两个类的值getset方法直接转换,效率可观,但是操作起来确实有些麻烦。这里我们提供一个更加便捷的工具类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
    /**
     * 该方法将javabean对象转换成protobuf对应的bean
     *
     * @param javaBean
     * @param protoBuilder
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static Object javaBeanToProtoBean(Object javaBean, Object protoBuilder) {
 
 
        try {
            Method mm = protoBuilder.getClass().getMethod("getDescriptorForType");
 
 
            Descriptors.Descriptor descriptor = (Descriptor) mm.invoke(protoBuilder);
 
 
            Field[] fields = javaBean.getClass().getDeclaredFields();
 
 
            for (Field item : fields) {
                try{
                String fName = item.getName();
                item.setAccessible(true);
                Object jObject = item.get(javaBean);
                if(null == jObject){
                   break;
                }
                FieldDescriptor fd = descriptor.findFieldByName(fName);
                if(null != fd){
                    if(fd.isRepeated()){
                        boolean isDefined = false;
                            Method[] mmm = protoBuilder.getClass().getMethods();
                            for(Method mItem : mmm){
                                 
                                try{
                                    String mName = mItem.getName();
                                    String mName1 = "add" + StringUtil.firstToUpper(fName);
                                    if(mName1.equals(mName) && mItem.getParameterTypes().length == 1){
                                        Class[] ccList = mItem.getParameterTypes();
                                        Class cc = ccList[0];
                                        Method me = cc.getMethod("newBuilder");
                                        Object oBuilder = me.invoke(null);//获取自定义对象builder
                                         
                                        List<Object> dList = (List<Object>) jObject; //数据为List集合
                                        List<Object> pBeanList = new ArrayList<Object>();
                                       for(Object oItem : dList){
                                          Object pBean = javaBeanToProtoBean(oItem,oBuilder);
                                          pBeanList.add(pBean);
                                       }
                                       Method mee = protoBuilder.getClass().getMethod("addAll"+StringUtil.firstToUpper(fName),Iterable.class);
                                       mee.invoke(protoBuilder, pBeanList);
                                        isDefined = true;
                                    }  
                                }catch(Exception e){
                                     
                                }
                             
                            }   
                      
                      if(!isDefined){
                        try{
                            Method me = protoBuilder.getClass().getMethod("addAll"+StringUtil.firstToUpper(fName),Iterable.class);
                            me.invoke(protoBuilder, jObject);
                        }catch(Exception e){
                            logger .info("this repeated field is a user-defined field");
                            e.printStackTrace();
                        }
                        }
 
 
                    }else{
                        boolean isDefined1 = false;
                        try{
                           // 自定义对象继续需要通过builder来解析处理,回调、       这一块很占计算时间。有待优化
                            Method bM = protoBuilder.getClass().getMethod("getFieldBuilder", FieldDescriptor.class);
                            Object subBuilder = bM.invoke(protoBuilder, fd);
                            Object pBean = javaBeanToProtoBean(jObject,subBuilder);
                            Method me = protoBuilder.getClass().getMethod("setField", FieldDescriptor.class, Object.class);
                            me.invoke(protoBuilder, fd, pBean);
                            isDefined1 = true;
                        }catch(Exception e){
//                          logger .info("this required field is not a user-defined field");
                        }
                         
                        if(!isDefined1){
                            Method me = protoBuilder.getClass().getMethod("setField", FieldDescriptor.class, Object.class);
                            me.invoke(protoBuilder, fd, jObject);
                        }
                         
                    }
                }
                }catch(Exception e){
                    logger .error("javaBeanToProtoBean method  item reflect error, item name:"+item.getName());
                }
            }
             
            Method buildM = protoBuilder.getClass().getMethod("build");
            Object rObject =  buildM.invoke(protoBuilder);
        /*    Method byteM = rObject.getClass().getMethod("toByteArray");
            Object byteObject =  byteM.invoke(rObject);
            byte[] pbByte = (byte[]) byteObject; 
            String pbStr = new String(Base64.getEncoder().encode(pbByte), "UTF-8");*/
            return rObject;
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("convert javabean to protobuf bean error,e:", e);
            return null;
        }
 
    }

  以上方法可以通用的讲前端发送过来的protobean转成我们需要的普通javabean,但是在性能上比getset慢上许多,普通项目用起来是没问题,也能达到每秒几万次,但是对性能有要求的童鞋可以关注我注释的那一行代码。

 
1
2
3
4
5
6
7
8
9
10
11
try{
                           // 自定义对象继续需要通过builder来解析处理,回调、       这一块很占计算时间。有待优化
                            Method bM = protoBuilder.getClass().getMethod("getFieldBuilder", FieldDescriptor.class);
                            Object subBuilder = bM.invoke(protoBuilder, fd);
                            Object pBean = javaBeanToProtoBean(jObject,subBuilder);
                            Method me = protoBuilder.getClass().getMethod("setField", FieldDescriptor.class, Object.class);
                            me.invoke(protoBuilder, fd, pBean);
                            isDefined1 = true;
                        }catch(Exception e){
//                          logger .info("this required field is not a user-defined field");
                        }

  由于转换中有这里要对包含其他bean做处理,所以在普通操作时经常进了catch代码块,所以浪费了很长时间(众所周知,catch是很浪费时间的),但是去掉这块代码转包含关系的bean就有问题,这块难题暂时博主也没解决,留给你们去,能解决的可以在下方留言。如果解决不了但是还是想简单方便的,可以关注我的下一篇博文,protostuff。

 
posted @   望星辰大海  阅读(21797)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· 展开说说关于C#中ORM框架的用法!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
点击右上角即可分享
微信分享提示