通讯协议序列化解读(一) 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 -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文件如下:
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使用教程
//以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。
通讯协议序列化解读(一) Protobuf详解教程的更多相关文章
- 通讯协议序列化解读(二) protostuff详解教程
上一篇文章 通讯协议序列化解读(一):http://www.cnblogs.com/tohxyblog/p/8974641.html 前言:上一面文章我们介绍了java序列化,以及谷歌protobu ...
- 转载 C# 序列化与反序列化意义详解
C# 序列化与反序列化意义详解 总结: ①序列化基本是指把一个对象保存到文件或流中,比如可以把文件序列化以保存到Xml中,或一个磁盘文件中②序列化以某种存储形式使自定义对象持久化: ③将对象从一个地方 ...
- HTTP协议头部与Keep-Alive模式详解
HTTP协议头部与Keep-Alive模式详解 .什么是Keep-Alive模式? 我们知道HTTP协议采用“请求-应答”模式,当使用普通模式,即非KeepAlive模式时,每个请求/应答客户和服务器 ...
- 搞懂分布式技术4:ZAB协议概述与选主流程详解
搞懂分布式技术4:ZAB协议概述与选主流程详解 ZAB协议 ZAB(Zookeeper Atomic Broadcast)协议是专门为zookeeper实现分布式协调功能而设计.zookeeper主要 ...
- Java线程通讯方法之wait()、nofity() 详解
Java线程通讯方法之wait().nofity() 详解 本文将探讨以下问题: synchronized 代码块使用 notify()与notifyAll()的区别 Java wait(),noti ...
- PHP cURL实现模拟登录与采集使用方法详解教程
来源:http://www.zjmainstay.cn/php-curl 本文将通过案例,整合浏览器工具与PHP程序,教你如何让数据 唾手可得 . 对于做过数据采集的人来说,cURL一定不会陌生.虽然 ...
- 【山外笔记-数据库】Memcached详解教程
本文打印版文档下载地址 [山外笔记-数据库]Memcached详解教程-打印版.pdf 一.Memcached数据库概述 1.Memcached简介 (1)Memcached是一个自由开源的,高性能, ...
- Jmeter(五) - 从入门到精通 - 创建网络计划实战和创建高级Web测试计划(详解教程)
1.简介 上一篇中宏哥已经将其的理论知识介绍了一下,这一篇宏哥就带着大家一步一步的把上一篇介绍的理论知识实践一下,然后再说一下如何创建高级web测试计划. 2.网络计划实战 通过上一篇的学习,宏哥将其 ...
- Iperf3网络性能测试工具详解教程
Iperf3网络性能测试工具详解教程 小M 2020年4月17日 运维 本文下载链接 [学习笔记]Iperf3网络性能测试工具.pdf 网络性能评估主要是监测网络带宽的使用率,将网络带宽利用最大化是保 ...
随机推荐
- Spring Data Jpa简单了解
原文来源:http://www.cnblogs.com/xuyuanjia/p/5707681.html 以下是自己简单整理原有文章,其实就是在原来文章基础上化重点以及可能会有所删减的方式进行整理,需 ...
- Hibernate(十一):映射继承关系的三种方案
背景: 在一些项目中,会采用集成的关系来定义数据库实体类,比如:人(Person)与学生(Student),学生来源与人,所以人的基本属性学生也拥有:但学生有的一些属性,人就不具有.人与学生之间很显然 ...
- POJ- 1094 Sorting It All Out---拓扑排序是否唯一的判断
题目链接: https://vjudge.net/problem/POJ-1094 题目大意: 该题题意明确,就是给定一组字母的大小关系判断他们是否能组成唯一的拓扑序列.是典型的拓扑排序,但输出格式上 ...
- html的基本结构
html的基本结构 1.<html>内容</html> html文档的文档标记,也称为html开始标记 这对标记分别位于网页的最前端和最后端,表示开始和结束 2.<hea ...
- pyspider爬取TripAdvisor
#!/usr/bin/env python # -*- encoding: utf-8 -*- # Created on 2017-06-11 10:10:53 # Project: london f ...
- 【阿里聚安全·安全周刊】 全美警局已普遍拥有破解 iPhone 的能力 | 女黑客破解任天堂Switch,称硬件漏洞无法修复
本周的七个关键词: 破解 iPhone丨 女黑客破解任天堂丨假的身份证 丨 扫黄打非丨华盛顿特区发现手机间谍设备 丨 Telegram被俄罗斯监管机构告上法庭丨价值5万美金的Firefox浏览器漏洞 ...
- bcrypt对密码加密的一些认识(学习笔记)
学习nodejs和mongoDB的时候,接触了用户注册和登录的一些知识. 1.关于增强用户密码的安全性 用户的密码肯定不能保存为明文,避免撞库攻击. 撞库攻击:撞库是一种针对数据库的攻击方式,方法是通 ...
- springboot集成jpa
spring data jpa简介 spring data jpa是spring基于hibernate及jpa规范封装出来的一套持久层框架.该框架极大的降低了开发者工作量,提升开发效率.提供的关键字可 ...
- MVC系列 引入MVC
1.必须的类库 system.web.Mvc system.Web.Razor system.web.webPages system.web.webpages.razor 添加方式如下图 2.MVC项 ...
- [NOI 2010]超级钢琴
Description 小Z是一个小有名气的钢琴家,最近C博士送给了小Z一架超级钢琴,小Z希望能够用这架钢琴创作出世界上最美妙 的音乐. 这架超级钢琴可以弹奏出n个音符,编号为1至n.第i个音符的美妙 ...