Netty-ProtobufVarint32

2022年6月16日 163点热度 0人点赞 0条评论

效果

  • ProtobufVarint32LengthFieldPrepender编码器用于在数据最前面添加Varint32,表示数据长度

    Untitled

  • ProtobufVarint32FrameDecoder是相对应的解码器

    Untitled

Varint32

讲编码器之前,先来讲讲什么是VarInt32(vary int 32),即:可变长的int

在java里,int的长度固定为 4 byte,即 32 bits,最高位为符号位。

而Varint32则不固定长度,最小 1 byte,最大 5 byte,每个byte的最高位如果为1表示下一个byte依然属于Varint32的,为0表示Varint32到当前byte结束。

所以在Varint32中,每个byte只有7bit存储数据。

下面以 398 举例:

// 数字398的二进制表示
110001110
// 在java中int的二进制表示
00000000 00000000 00000001 10001110
// Varint32的二进制表示
10001110 00000011

转换步骤如下:

  1. 398的二进制表示为110001110,总共有9位
  2. 因为398长度为9bit,大于7,所以在Varint32中需要2个byte来存储
  3. Varint32第一个byte存398的低7位0001110,最高位置1表示还未存储完成,即:10001110
  4. Varint32第二个byte存398的后面两位11,最高位置0表示已存储完成,00000011

最后,别问我负数怎么表示,问神奇的海螺。

ProtobufVarint32LengthFieldPrepender

此编码器的作用,就是将数据长度从int转成Varint32,并添加在数据流的最前面。

  1. 查看源码,其入口为decode方法:
    @Override
    protected void encode(
            ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception {
            // 获取数据长度
        int bodyLen = msg.readableBytes();
            // 计算int转成Varint32所需字节
        int headerLen = computeRawVarint32Size(bodyLen);
            // 安全扩充缓冲区
        out.ensureWritable(headerLen + bodyLen);
            // 将bodyLen转成Varint32并写入
        writeRawVarint32(out, bodyLen);
            // 写入原有的数据msg
        out.writeBytes(msg, msg.readerIndex(), bodyLen);
    }
    
  2. 先来看看如何计算int转成Varint32所需字节
    static int computeRawVarint32Size(final int value) {
            // value的低7位有数据,其余位为0
        if ((value & (0xffffffff <<  7)) == 0) {
            return 1;
        }
            // value的低14位有数据,其余位为0
        if ((value & (0xffffffff << 14)) == 0) {
            return 2;
        }
        if ((value & (0xffffffff << 21)) == 0) {
            return 3;
        }
        if ((value & (0xffffffff << 28)) == 0) {
            return 4;
        }
        return 5;
    }
    
    // 0xffffffff 的二进制表示
    11111111 11111111 11111111 11111111
    // 0xffffffff << 7 的二进制表示
    11111111 11111111 11111111 10000000
    // 假设value为100,二进制表示
    00000000 00000000 00000000 01100100
    // value与0xffffffff << 7按位与,即value & (0xffffffff <<  7)
    11111111 11111111 11111111 10000000
    &
    00000000 00000000 00000000 01100100
    =
    00000000 00000000 00000000 00000000 // 结果等于0
    // 假设value为398,value与0xffffffff << 7按位与
    11111111 11111111 11111111 10000000
    &
    00000000 00000000 00000001 10001110
    =
    00000000 00000000 00000001 10000000 // 结果等于384,大于0
    
  3. 再来看看将bodyLen转成Varint32并写入
    static void writeRawVarint32(ByteBuf out, int value) {
        while (true) {
                    // value的低7位有数据,其余位为0
            if ((value & ~0x7F) == 0) {
                out.writeByte(value);
                return;
            } else {
                            // 取value的低7位,最高位置1
                out.writeByte((value & 0x7F) | 0x80);
                            // 右移7位
                value >>>= 7;
            }
        }
    }
    

ProtobufVarint32FrameDecoder

此解码器的作用,是将Varint32 + data,转换成 data

  1. 源码入口
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
            throws Exception {
            // 标记读取索引,用户后续的恢复
        in.markReaderIndex();
            // 读取前的索引位置
        int preIndex = in.readerIndex();
            // 读取字节,将Varint32转成int
        int length = readRawVarint32(in);
            // 读取后索引位置等于读取前,表示读取不成功
        if (preIndex == in.readerIndex()) {
            return;
        }
        if (length < 0) {
            throw new CorruptedFrameException("negative length: " + length);
        }
            // 如果netty读取到的字节长度不满足数据长度,则重置读取索引
        if (in.readableBytes() < length) {
            in.resetReaderIndex();
        } else {
            out.add(in.readRetainedSlice(length));
        }
    }
    
  2. 读取字节,将Varint32转成int
    private static int readRawVarint32(ByteBuf buffer) {
        if (!buffer.isReadable()) {
            return 0;
        }
        buffer.markReaderIndex();
        byte tmp = buffer.readByte();
            // tmp >= 0,则byte最高位为0,证明Varint32长度为1byte
        if (tmp >= 0) {
            return tmp;
        } else {
                    // result取temp的低7位
            int result = tmp & 127;
                    // Varint还没结束,但netty读不到更多字节了,则返回
            if (!buffer.isReadable()) {
                buffer.resetReaderIndex();
                return 0;
            }
                    // 读取下一个字节,并判断Varint是否在该字节结束
            if ((tmp = buffer.readByte()) >= 0) {
                result |= tmp << 7;
            } else {
                            // 如果Varint在第二个字节还没结束,则取第二个字节的低7位
                result |= (tmp & 127) << 7;
                if (!buffer.isReadable()) {
                    buffer.resetReaderIndex();
                    return 0;
                }
                if ((tmp = buffer.readByte()) >= 0) {
                    result |= tmp << 14;
                } else {
                    result |= (tmp & 127) << 14;
                    if (!buffer.isReadable()) {
                        buffer.resetReaderIndex();
                        return 0;
                    }
                    if ((tmp = buffer.readByte()) >= 0) {
                        result |= tmp << 21;
                    } else {
                        result |= (tmp & 127) << 21;
                        if (!buffer.isReadable()) {
                            buffer.resetReaderIndex();
                            return 0;
                        }
                        result |= (tmp = buffer.readByte()) << 28;
                        if (tmp < 0) {
                            throw new CorruptedFrameException("malformed varint.");
                        }
                    }
                }
            }
            return result;
        }
    }
    

王谷雨

一个苟且偷生的java程序员

文章评论