1. 二次解码器
如果说封装成帧是一次解码器,那么二次解码器就是将byte流转化成java对象等。
一次解码器和二次解码器的区别,区别一次解码器,而不是将两个两个解码器合二为一主要是因为考虑到分层的思想。如果有一天protobuf协议变成json协议,代码耦合度高,修改代码量大。
2. ObjectEncoder如何实现编码的
进入源码如图所示的包路径,第48行就是编码的核心方式,看得出来使用的是CompactObjectOutputStream
的方式,也就是压缩了数据:
跟进CompactObjectOutputStream
类
2.1 writeStreamHeader简化了STREAM_MAGIC字段
jdk自带的writeStreamHeader()
方法中带有STREAM_MAGIC
来标志当前协议类型,CompactObjectOutputStream
类没有,用于节省空间
下图是java的jdk源码:
下面是netty的codec源码:
2.2 writeClassDescriptor简化了元信息
CompactObjectOutputStream
对非原始数据类型,写入类的描述信息只有两行代码,
下面是jdk的代码:写入了类的大量的元信息。
void writeNonProxy(ObjectOutputStream out) throws IOException {
out.writeUTF(name); // 写入类名
out.writeLong(getSerialVersionUID());//类的序列号
byte flags = 0;//类的标志位
if (externalizable) {
flags |= ObjectStreamConstants.SC_EXTERNALIZABLE;
int protocol = out.getProtocolVersion();
if (protocol != ObjectStreamConstants.PROTOCOL_VERSION_1) {
flags |= ObjectStreamConstants.SC_BLOCK_DATA;
}
} else if (serializable) {
flags |= ObjectStreamConstants.SC_SERIALIZABLE;
}
if (hasWriteObjectData) {
flags |= ObjectStreamConstants.SC_WRITE_METHOD;
}
if (isEnum) {
flags |= ObjectStreamConstants.SC_ENUM;
}
out.writeByte(flags);
out.writeShort(fields.length);
// 循环写入对象的每个字段的类型和名称
for (int i = 0; i < fields.length; i++) {
ObjectStreamField f = fields[i];
out.writeByte(f.getTypeCode());
out.writeUTF(f.getName());
// 如果不是原始类型,即是对象或者Interface
// 则会写入表示对象或者类的类型字符串
if (!f.isPrimitive()) {
out.writeTypeString(f.getTypeString());
}
}
}
那么,既然这么多的描述信息都被删除了,那怎么实现反序列化呢?
关键就在于48行,写入了一个类的名称。同时看到下图左上角,包内有一个ClassLoader,它是利用反射去实现反序列化的。
总结:ObjectEncoder
简化了魔术和元信息来减少数据的传输,使用反射的方式实现对原来的对象的反序列化。
3.protobuf的编码解码过程
WorldClockClientInitializer
是netty中提供一个整合protobuf例子,提供了四个编码解码器
3.1 ProtobufVarint32FrameDecoder
ProtobufVarint32FrameDecoder
是一个类似于LengthFieldBasedFrameDecoder
,用一个字段存储length,然后data字段存储数据。
它做出的改进是,会根据根据数据的长度使用readRawVarint32
方法去按照varint32的方式去读取头部的length长度。里面的int字段可变长度的就是他做出的改进。
3.2 ProtobufDecoder
ProtobufDecoder
核心步骤:
1.类初始化的时候,判断是否含所有getParserForType
方法,如果有HAS_PARSER
为true
2.decode
方法中,如果带有extension则按照扩展的解析方式解析,如果没有根据HAS_PARSER
字段判断是mergeFrom()
还是parseFrom()
解析
@Sharable
public class ProtobufDecoder extends MessageToMessageDecoder<ByteBuf> {
// HAS_PARSER是版本大于2.5.0才有的方法,旧版本的protobuf没有该方法
// HAS_PARSER用于标志protobuf中是否有getParserForType方法
private static final boolean HAS_PARSER;
static {
boolean hasParser = false;
try {
// MessageLite.getParserForType() is not available until protobuf 2.5.0.
MessageLite.class.getDeclaredMethod("getParserForType");// 反射获取getParserForType方法
hasParser = true;
} catch (Throwable t) {
// Ignore
}
HAS_PARSER = hasParser;
}
private final MessageLite prototype;
private final ExtensionRegistryLite extensionRegistry;
/**
* Creates a new instance.
*/
public ProtobufDecoder(MessageLite prototype) {
this(prototype, null);
}
public ProtobufDecoder(MessageLite prototype, ExtensionRegistry extensionRegistry) {
this(prototype, (ExtensionRegistryLite) extensionRegistry);
}
public ProtobufDecoder(MessageLite prototype, ExtensionRegistryLite extensionRegistry) {
this.prototype = ObjectUtil.checkNotNull(prototype, "prototype").getDefaultInstanceForType();
this.extensionRegistry = extensionRegistry;
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out)
throws Exception {
final byte[] array;
final int offset;
final int length = msg.readableBytes();
if (msg.hasArray()) {
array = msg.array();
offset = msg.arrayOffset() + msg.readerIndex();
} else {
array = ByteBufUtil.getBytes(msg, msg.readerIndex(), length, false);
offset = 0;
}
// 带有extension字段则按照扩展的规则进行解析
if (extensionRegistry == null) {
// HAS_PARSER表示是否含有getParserForType方法
if (HAS_PARSER) {
// 使用parseFrom方法解析
out.add(prototype.getParserForType().parseFrom(array, offset, length));
} else {
// 使用mergeFrom方法解析
out.add(prototype.newBuilderForType().mergeFrom(array, offset, length).build());
}
} else {
if (HAS_PARSER) {
out.add(prototype.getParserForType().parseFrom(
array, offset, length, extensionRegistry));
} else {
out.add(prototype.newBuilderForType().mergeFrom(
array, offset, length, extensionRegistry).build());
}
}
}
}
3.3 ProtobufVarint32LengthFieldPrepender
通过可变数据类型的规则解析body的数据长度
3.4 ProtobufEncoder
将数据转化为字节数据用于传传输
总结:protobuf对数据长度处理使用可变数据类型,减少数据的传输压力。