效果
- ProtobufVarint32LengthFieldPrepender编码器用于在数据最前面添加
Varint32
,表示数据长度 -
ProtobufVarint32FrameDecoder是相对应的解码器
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
转换步骤如下:
- 398的二进制表示为110001110,总共有9位
- 因为398长度为9bit,大于7,所以在Varint32中需要2个byte来存储
- Varint32第一个byte存398的低7位0001110,最高位置1表示还未存储完成,即:10001110
- Varint32第二个byte存398的后面两位11,最高位置0表示已存储完成,00000011
最后,别问我负数怎么表示,问神奇的海螺。
ProtobufVarint32LengthFieldPrepender
此编码器的作用,就是将数据长度从int转成Varint32,并添加在数据流的最前面。
- 查看源码,其入口为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); }
- 先来看看如何计算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
- 再来看看将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
- 源码入口
@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)); } }
- 读取字节,将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; } }