通讯协议序列化解读(一) 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++ Type

Java Type

Python 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导包
  <!-- 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编译器:

protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR --go_out=DST_DIR --ruby_out=DST_DIR --javanano_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto
  • IMPORT_PATH声明了一个.proto文件所在的解析import具体目录。如果忽略该值,则使用当前目录。如果有多个目录则可以多次调用--proto_path,它们将会顺序的被访问并执行导入。-I=IMPORT_PATH--proto_path的简化形式。
  • 当然也可以提供一个或多个输出路径: 

作为一个方便的拓展,如果DST_DIR以.zip或者.jar结尾,编译器会将输出写到一个ZIP格式文件或者符合JAR标准的.jar文件中。注意如果输出已经存在则会被覆盖,编译器还没有智能到可以追加文件。  - 你必须提议一个或多个.proto文件作为输入,多个.proto文件可以只指定一次。虽然文件路径是相对于当前目录的,每个文件必须位于其IMPORT_PATH下,以便每个文件可以确定其规范的名称。

2.2.3protobuf使用教程
//以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方法直接转换,效率可观,但是操作起来确实有些麻烦。这里我们提供一个更加便捷的工具类。

	/**
	 * 该方法将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慢上许多,普通项目用起来是没问题,也能达到每秒几万次,但是对性能有要求的童鞋可以关注我注释的那一行代码。

 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。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

3 条评论
登录 后参与评论

相关文章

来自专栏帘卷西风的专栏

关于lua扩展库lpack的使用指南

lpack的具体用法 1、打包接口pack的使用,全局名字容易混淆lua本身函数unpack,使用string.pack好些,也可以修改源码修改函数名。

633
来自专栏遊俠扎彪

C++ 的 Name Mangling

编程语言组织程序,都有一定的可见范围,比如Java的包、C/C++的文件。就像我们平时使用的文件夹一样,有了这些组织机制,变量、函数的命名在一定程度上就可以重复...

3300
来自专栏程序员与猫

Go Code Review Comments 译文(截止2018年7月27日)

持续更新中… 原文最新链接 https://github.com/golang/go/wiki/CodeReviewComments/5a40ba36d38...

643
来自专栏恰同学骚年

设计模式的征途—1.单例(Singleton)模式

  单例模式属于创建型模式的一种,创建型模式是一类最常用的设计模式,在软件开发中应用非常广泛。创建型模式将对象的创建和使用分离,在使用对象时无需关心对象的创建细...

962
来自专栏每日一篇技术文章

OpenGL ES _ 着色器_预处理器

编译GLSL 着色器的第一个步骤是由预处理进行解析的。你可能还是不知道干啥的,它的作用就是删除注释、包含其他文件以及执行宏(宏macro是一段重复文字的简短描写...

581
来自专栏贾鹏辉的技术专栏@CrazyCodeBoy

【Android开发高级实践】轻松帮你发现Bug(FindBugs在AndroidStudio上的应用)

  在日常开发过程中难免会因为一时疏忽而留下一些Bug,这些Bug就是埋在程序里的定时炸弹,如果不能及时铲除就会导致程序的不稳定,异常或闪退的现象,从而导致用户...

2846
来自专栏高性能服务器开发

服务器开发通信协议设计介绍

由于我们的即时通讯软件的用户存在用户状态问题,即用户登录成功以后可以在他的好友列表中看到哪些好友在线,所以客户端和服务器需要保持长连接状态。另外即时通讯软件一般...

882
来自专栏Java成神之路

【转】零基础写Java知乎爬虫之进阶篇

说到爬虫,使用Java本身自带的URLConnection可以实现一些基本的抓取页面的功能,但是对于一些比较高级的功能,比如重定向的处理,HTML标记的去除,仅...

903
来自专栏Java Web

Java 7的新特性

前言 看大佬推荐的书单买了一本《Java 8实战》,总觉得在了解Java 8之前,是不是也应该去了解了解一下Java 7的一些特性?所以就自己百度了一些资料来...

3055
来自专栏Java Edge

IoC容器系列的设计与实现(基于Spring5.0.4)1 Spring的IoC容器系列2 Spring IoC容器的设计3 BeanFactory的应用场景4 BeanFactory容器的设计原理5

3026

扫码关注云+社区