通信協議序列化解讀(一) Protobuf詳解教程

前言:說到JSON可能你們很熟悉,是目前應用最普遍的一種序列化格式,它使用起來簡單方便,並且擁有超高的可讀性。可是在愈來愈多的應用場景裏,JSON冗長的缺點致使它並非一種最優的選擇。html

1、經常使用序列化格式介紹

目前JAVA經常使用的序列化有protobuf,json,xml,Serializable,hessian,kryo。他們的優缺點以下:前端

  • JSON:很少說了,用途普遍,序列化方式還衍生了阿里的fastjson,美團的MSON,谷歌的GSON等更加優秀的轉碼工具。
    優勢:使用方便。
    缺點:數據冗長,轉碼性能通常。java

  • XML:好久以前的轉碼方法了,如今用的很少。
    優勢:暫時沒發現。
    缺點:數據冗長,轉碼性能通常。python

  • Serialzable:JDK自帶的序列化。
    優勢:使用方便。
    缺點:轉碼性能低下。git

  • hessian:基於 binary-RPC實現的遠程通信library,使用二進制傳輸數據。
    優勢:數據長度小。
    缺點:性能低下。
    github

說了這麼多,全是性能低下,MMP一羣智障兒?固然不是!kryo就是一款快速、高效的序列化框架,可是它不是咱們今天的主角,由於他只能在java中使用,和前端非java語言的通信就存在極大的隔閡。咱們今天的主角是protobuf?emmm,算是吧,可是也不全是,先給你們說下protobuf吧。數據庫

  • protobuf:谷歌公司出的一款開源項目,性能好,效率高,而且支持多種語言,例如:java,C++,python等。

    優勢:轉碼性能高,支持多語言。
    缺點:中文文檔少,使用相對複雜。




json

2、protobuf詳解

在使用protobuf以前,須要安裝protobuf編譯器和運行時環境。
因爲protobuf是跨平臺,跨語言的,因此須要下載和安裝對應版本的編譯器和運行時依賴。markdown

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());

  

3、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。

相關文章
相關標籤/搜索