ThriftRPC——二進制文件傳輸

       公司最近有個新的需求,需要和設備之間進行文件傳輸,字符型文件傳輸很好解決,但是二進制類型的文件傳輸存在比較多的坑,現在將存在的坑進行歸納整理:

       注意:閱讀這篇文章的讀者需要自行去了解thritrpc的相關知識,這裏我們不做過多的介紹。

1. 準備工作

        文件格式分別爲:py和bit兩種文件,其中py文件較小,是一個類xml類型的文件,而bit則爲一個純二進制的文件

        因爲涉及到的文件不多,我們這裏使用mysql進行文件存儲,這裏我們遇到第一個坑,mysql的value存儲上限

        這裏我使用的是mysql 5.7 版本,在my.ini配置文件中我們可以看到如下的配置:

        max_allowed_packet = 4M

        這個4M是默認的上限,因爲文件大小很有可能超過4M,這裏我們根據文件的大小將這個值設置爲自己需要的大小,具體的設置方式網上有很多資料,這裏我們不加贅述;

       這裏存儲字段的存儲類型我選擇mediumblob,與其相關的字段類型如下,開發人員可以根據自己的需求選擇合適的字段類型。

        ①TinyBlob類型  最大能容納255B的數據

        ②Blob類型  最大能容納65KB的

        ③MediumBlob類型  最大能容納16MB的數據

        ④LongBlob類型  最大能容納4GB的數據

2. thriftRPC描述文件設置:

struct ReqWaveLoad{
	1:string verFlag,
	2:i16 dataType,
	3:string ip,
	4:i32    waveId,
	5:string waveName,
	6:string waveXml,    //字符型文件我們可以使用string類型直接傳輸,親測沒有問題
	7:binary waveFpga    //二進制類型文件需要使用binary類型傳輸
}

        從上面的配置我們可以看到,我們在傳輸文件的時候針對不同類型的文件的選擇方式,爬坑之路由此開始

3. 爬坑1——java中byte[]和String

        最開始,因爲mysql blob類型對應的java字段類型爲byte[],爲了傳輸的準確性,我決定將兩個文件的byte[]都轉換爲String類型進行傳輸,此時因爲文件中內容的不一樣,二進制文件出現問題:

        存入數據庫的文件差不多3.9M,而轉換成String類型後,寫入到本地的文件大小變爲4.5M

        因爲二進制文件沒有辦法打開,我將生成的文件放到設備時運行,確認文件中內容無法正常使用,經過多番測試,本人認爲應該是String在轉換二進制文件內容時,因爲編碼的問題導致文件亂碼,但此結論有待進一步驗證。

        基於此原因,如果你是傳輸字符型文件(一般編輯器都能打開,可以正常顯示),那麼此方式沒有任何的問題,

 

4. 爬坑2——byte[]和json

       在我將數據類型轉換爲binary時,在生成的thriftRPC文件中我們看到的是,waveFpga 對應的數據類型爲ByteBuffer,這個數據類型用的比較少,同時我發現ByteBuffer類型的數據在json中並不被支持,發送的數據接受方沒有辦法解析,應對方式有兩種:

        - 在JSON中添加ByteBuffer的序列化方式,實現ByteBuffer類型的序列化和反序列化

        - 將從數據庫中拿出來的byte數組轉換爲String類型,然後傳遞到接收方

  public String verFlag; // required
  public short dataType; // required
  public String ip; // required
  public int waveId; // required
  public String waveName; // required
  public String waveXml; // required
  public ByteBuffer waveFpga; // required

4.1 序列化方式

      參考博客:https://blog.csdn.net/10km/article/details/78151366

       首先添加序列化工具類

package com.hjkj.nms_satellitecom_manager.util;

import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.parser.DefaultJSONParser;
import com.alibaba.fastjson.parser.JSONToken;
import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer;
import com.alibaba.fastjson.serializer.JSONSerializer;
import com.alibaba.fastjson.serializer.ObjectSerializer;
import com.alibaba.fastjson.serializer.PrimitiveArraySerializer;
import com.alibaba.fastjson.serializer.SerializerFeature;

import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.ByteBuffer;

public class ByteBufferCodec implements ObjectSerializer, ObjectDeserializer {
    public static ByteBufferCodec instance = new ByteBufferCodec();
    @SuppressWarnings("unchecked")
    @Override
    public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
        int token = parser.lexer.token();
        if (token == JSONToken.NULL) {
            parser.lexer.nextToken();
            return null;
        } else if(token == JSONToken.HEX  || token == JSONToken.LITERAL_STRING){
            byte[] bytes = parser.lexer.bytesValue();
            parser.lexer.nextToken();
            return (T) ByteBuffer.wrap(bytes);
        }
        throw new JSONException(String.format("invalid '%s' for ByteBuffer",JSONToken.name(token)));
    }

    @Override
    public int getFastMatchToken() {
        return JSONToken.LITERAL_STRING;
    }
    /**
     * 返回buffer中所有字節(position~limit),不改變buffer狀態
     * @param buffer
     * @return
     */
    private static final byte[] getAllBytesInBuffer(ByteBuffer buffer){
        int pos = buffer.position();
        try{
            byte[] bytes = new byte[buffer.remaining()];
            buffer.get(bytes);
            return bytes;
        }finally{
            buffer.position(pos);
        }
    }
    /**
     * 直接引用{@link PrimitiveArraySerializer}實現序列化
     */
    @Override
    public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features)
            throws IOException {
        if ( (object instanceof ByteBuffer) ) {
            PrimitiveArraySerializer.instance.write(serializer, getAllBytesInBuffer((ByteBuffer)object), fieldName, fieldType, features);
        }else{
            serializer.out.writeNull(SerializerFeature.WriteNullListAsEmpty);
        }

    }
}

        而後看使用:

    @Test
    public void test() {
        ByteBuffer byteBuffer =ByteBuffer.wrap(new byte[]{22,33,3,2,3,1,5,-1});
        // 修改ParserConfig.global全局變量,將ByteBufferCodec加入反序列化器容器
        ParserConfig.global.putDeserializer(ByteBuffer.class, ByteBufferCodec.instance);
        // 修改SerializeConfig.globalInstance全局變量,將ByteBufferCodec加入序列化器容器
        // 對應的Class爲 java.nio.HeapByteBuffer
        SerializeConfig.globalInstance.put(ByteBuffer.wrap(new byte[]{}).getClass(), ByteBufferCodec.instance);
        // 待序列化的 ByteBuffer 對象
        ByteBuffer byteBuffer =ByteBuffer.wrap(new byte[]{22,33,3,2,3,1,5,-1});
        // 序列化
        String serString = JSON.toJSONString(group.byteBuffer);
        System.out.println(serString);
        // 反序列化
        ByteBuffer deserialedByteBuffer = JSON.parseObject(serString,ByteBuffer.class);
        System.out.println(JSON.toJSONString(deserialedByteBuffer));
    }

4.2 使用byte數組轉String

      這裏涉及到的主要是將byte數組轉換爲基於base64的String類型,因爲base64無編碼形式的

//byte[]轉String
Base64.encodeToString(wave.getBitValue());    //wave.getBitValue()得到的是一個byte[]
//String轉byte[]
new BASE64Decoder().decodeBuffer("獲取到的String類型的數據");

5. 爬坑3——thrift中的ByteBuffer

        最開始我給ByteBuffer賦值是基於javabean的常規形式賦值的,但是出現在測試中發現,數據並沒有被添加到此字段中,我 獲取的數據長度一直爲0,查看 生成的javabean發現如下代碼:

  public byte[] getWaveFpga() {
    setWaveFpga(org.apache.thrift.TBaseHelper.rightSize(waveFpga));
    return waveFpga == null ? null : waveFpga.array();
  }

  public ByteBuffer bufferForWaveFpga() {
    return waveFpga;
  }

  public ReqWaveLoad setWaveFpga(byte[] waveFpga) {
    setWaveFpga(waveFpga == null ? (ByteBuffer)null : ByteBuffer.wrap(waveFpga));
    return this;
  }

  public ReqWaveLoad setWaveFpga(ByteBuffer waveFpga) {
    this.waveFpga = waveFpga;
    return this;
  }

      可以看到的是,setWaveFpga有一個重載的方法,其參數可以是ByteBuffer類型也可以是byte數組類型,理論上來講,使用這兩個方法都可以拿到對應的數據,但是事實上這兩種方法單純的去用來賦值都不能成功,其內部機制和常規的javabean存在不同,首先,返回值就不同,正常的set方法的返回值都是void,但是這個返回的是當前的對象,因爲其是未編碼的字節流,thrift內部處理方式可能不同,目前還不知道具體原因,有待日後進行調整

       那麼我們如何解決這個問題呢?最終測試,我們採用如下方法可以成功的爲變量賦值:

 ReqWaveLoad reqWave = new ReqWaveLoad();
 reqWave.waveFpga = ByteBuffer.wrap(waveFpga);;
 reqWave.setDataType(ThriftCMD.THRIFT_EQUIPMENT_WAVE_LOAD);
 reqWave.setVerFlag(ThriftSignal.THRIFT_PROTOCOL_VERSION);
 reqWave.setWaveName(waveName);
 reqWave.setWaveId(waveId);
 reqWave.setIp(ip);
 reqWave.setWaveXml(waveXml);

      經過測試後,使用此方式問題解決!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章