公司最近有個新的需求,需要和設備之間進行文件傳輸,字符型文件傳輸很好解決,但是二進制類型的文件傳輸存在比較多的坑,現在將存在的坑進行歸納整理:
注意:閱讀這篇文章的讀者需要自行去了解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);
經過測試後,使用此方式問題解決!!!