Netty源码(四):Netty的“二次解码器”MessageToMessageDecoder

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对数据长度处理使用可变数据类型,减少数据的传输压力。

发布了275 篇原创文章 · 获赞 42 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/qq_35688140/article/details/105483880
今日推荐