设为首页 加入收藏

TOP

Netty Protobuf处理粘包分析(一)
2023-07-26 08:16:39 】 浏览:93
Tags:Netty Protobuf 包分析

背景

最近消息中间件项目进行联调,我负责Server端,使用Java的Netty框架。同事负责Client端,使用Go的net包,消息使用Protobuf序列化。联调时Client发送的消息Server端解析出错,经过分析发现是Server与Client粘包处理方式不一致导致,Server使用的是Protobuf提供的粘包处理方式,Client使用的是消息头定义长度的处理方式,探索一下Protobuf粘包处理方式有何不同。

编码类

public class ProtobufVarint32LengthFieldPrepender extends MessageToByteEncoder<ByteBuf> {

    @Override
    protected void encode(
            ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception {
        int bodyLen = msg.readableBytes();
        int headerLen = computeRawVarint32Size(bodyLen);
        out.ensureWritable(headerLen + bodyLen);
        writeRawVarint32(out, bodyLen);
        out.writeBytes(msg, msg.readerIndex(), bodyLen);
    }

    /**
     * Writes protobuf varint32 to (@link ByteBuf).
     * @param out to be written to
     * @param value to be written
     */
    static void writeRawVarint32(ByteBuf out, int value) {
        while (true) {
            if ((value & ~0x7F) == 0) {
                out.writeByte(value);
                return;
            } else {
                out.writeByte((value & 0x7F) | 0x80);
                value >>>= 7;
            }
        }
    }

    /**
     * Computes size of protobuf varint32 after encoding.
     * @param value which is to be encoded.
     * @return size of value encoded as protobuf varint32.
     */
    static int computeRawVarint32Size(final int value) {
        if ((value & (0xffffffff <<  7)) == 0) {
            return 1;
        }
        if ((value & (0xffffffff << 14)) == 0) {
            return 2;
        }
        if ((value & (0xffffffff << 21)) == 0) {
            return 3;
        }
        if ((value & (0xffffffff << 28)) == 0) {
            return 4;
        }
        return 5;
    }
}

encode()方法

protected void encode(
            ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception {
        // 获取消息长度
	int bodyLen = msg.readableBytes(); 
	// 计算表示消息体长度所需的字节数量
        int headerLen = computeRawVarint32Size(bodyLen);
	// 拿到所有需要写入的数据长度,对缓冲区进行扩容
        out.ensureWritable(headerLen + bodyLen);
	// 将表示消息体长度的字节写入缓冲区
        writeRawVarint32(out, bodyLen);
        out.writeBytes(msg, msg.readerIndex(), bodyLen);
    }

writeRawVarint32()方法

先看value & ~0x7F(value & 0x7F) | 0x80value >>>= 7这几个看不懂的地方,&|~>>>=这些符号为计算机的位运算符号,分别代表与、或、非、忽略符号位右移(a>>>=n 相当于 a = a>>>n

计算value & ~0x7F

分别假设value值为100200

100转二进制为01100100200转二进制为11001000

计算100 & ~0x7F

十进制 十六进制 运算符


100 0x64 0 1 1 0 0 1 0 0
-128 ~0x7f & 1 0 0 0 0 0 0 0
0 0x00
0 0 0 0 0 0 0 0

计算200 & ~0x7F

十进制 十六进制 运算符


200 0xc8 1 1 0 0 1 0 0 0
-128 ~0x7f & 1 0 0 0 0 0 0 0
128 0x80
1 0 0 0 0 0 0 0

这里运算结果使用十进制表示二进制是不准确的,仅作参考,需要根据数据类型进行转换,比如:10000000转换为byte类型是-128,转换为int是128

通过以上计算可以看出:

可以使用小于7个位表示的数字即可满足条件,7个位可以表示$2^7=128$个数字,取值范围是0~127,也就是说0~127可以满足条件,这一步的目的是保证写入表示消息体长度的最后一位字节是正数,后面会说到。

value=100满足条件,所以向bytebuf中写入字节01100100,然后return方法结束。

value=200不满足条件,那么看(value & 0x7F) | 0x80这一步运算。

计算(value & 0x7F) | 0x80

十进制 十六进制 运算符


200 0xc8 1 1 0 0 1 0 0 0
127 0x7f & 0 1 1 1 1 1 1 1
- - - - - - - - - - -
72 0x48
0 1 0 0 1 0 0 0
128 0x80 | 1 0 0 0 0 0 0 0
- - - - - - - - - - -
200 0xc8 1 1 0 0 1 0 0 0

计算结果还是200,我们分析一下步骤:

value & 0x7f:取出最后七个位,|0x80:将首位转为1

即取出最后7个位,高位补1,正好一个字节的长度,将11001000写入bytebuf,再看value >>>= 7

计算value >>>= 7

十进制 运算符


200 1 1 0 0 1 0 0 0

>>>






1 1 0 0 1 0 0 0
1
0 0 0 0 0 0
首页 上一页 1 2 3 下一页 尾页 1/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Ubuntu玩机记录,让我破电脑又飞.. 下一篇day05-SpringMVC底层机制简单实现..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目