公司最近有个新的需求,需要和设备之间进行文件传输,字符型文件传输很好解决,但是二进制类型的文件传输存在比较多的坑,现在将存在的坑进行归纳整理:
注意:阅读这篇文章的读者需要自行去了解thritrpc的相关知识,这里我们不做过多的介绍。
文件格式分别为: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的数据
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类型传输
}
从上面的配置我们可以看到,我们在传输文件的时候针对不同类型的文件的选择方式,爬坑之路由此开始
最开始,因为mysql blob类型对应的java字段类型为byte[],为了传输的准确性,我决定将两个文件的byte[]都转换为String类型进行传输,此时因为文件中内容的不一样,二进制文件出现问题:
存入数据库的文件差不多3.9M,而转换成String类型后,写入到本地的文件大小变为4.5M
因为二进制文件没有办法打开,我将生成的文件放到设备时运行,确认文件中内容无法正常使用,经过多番测试,本人认为应该是String在转换二进制文件内容时,因为编码的问题导致文件乱码,但此结论有待进一步验证。
基于此原因,如果你是传输字符型文件(一般编辑器都能打开,可以正常显示),那么此方式没有任何的问题,
在我将数据类型转换为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
参考博客: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 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));
}
这里涉及到的主要是将byte数组转换为基于base64的String类型,因为base64无编码形式的
//byte[]转String
Base64.encodeToString(wave.getBitValue()); //wave.getBitValue()得到的是一个byte[]
//String转byte[]
new BASE64Decoder().decodeBuffer("获取到的String类型的数据");
最开始我给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);
经过测试后,使用此方式问题解决!!!