目录
二次解码
ObjectEncoder 如何实现编码
1、 writeStreamHeader
2、writeClassDescriptor
总结
protobuf的编码解码过程
1、ProtobufVarint32FrameDecoder
2、ProtobufDecoder
3、ProtobufVarint32LengthFieldPrepender
4、ProtobufEncoder
总结
我们将数据帧 转为正确的byte数组 我们称之为一次解码
将byte数据流转换为java对象 , 我们称之为二次解码
一次解码是将数据正确的拆分或者拼合 , 二次解码是将数据正确的序列化或者反序列化 。
这里用到了分层的思想 , 一边后续协议变更时,高耦合度代码修改量大的问题
进入源码如图所示的包路径,第48行就是编码的核心方式,看得出来使用的是CompactObjectOutputStream
的方式,也就是压缩了数据
writeStreamHeader简化了STREAM_MAGIC字段
jdk自带的writeStreamHeader()
方法中带有STREAM_MAGIC
来标志当前协议类型,CompactObjectOutputStream
类没有,用于节省空间
下图是java的jdk源码:
CompactObjectOutputStream
对非原始数据类型,写入类的描述信息只有两行代码
那么,既然这么多的描述信息都被删除了,那怎么实现反序列化呢? 关键就在于48行,写入了一个类的名称。同时看到下图左上角,包内有一个ClassLoader,它是利用反射去实现反序列化的。
ObjectEncoder
简化了魔术和元信息来减少数据的传输,使用反射的方式实现对原来的对象的反序列化。
WorldClockClientInitializer
是netty中提供一个整合protobuf例子,提供了四个编码解码器
ProtobufVarint32FrameDecoder是一个类似于LengthFieldBasedFrameDecoder,用一个字段存储length,然后data字段存储数据。 它做出的改进是,会根据根据数据的长度使用readRawVarint32方法去按照varint32的方式去读取头部的length长度。里面的int字段可变长度的就是他做出的改进。
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()); } } } }
通过可变数据类型的规则解析body的数据长度
将数据转化为字节数据用于传传输
protobuf对数据长度处理使用可变数据类型,减少数据的传输压力。