背景
最近消息中间件项目进行联调,我负责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) | 0x80
、value >>>= 7
这几个看不懂的地方,&
、|
、~
、>>>=
这些符号为计算机的位运算符号,分别代表与、或、非、忽略符号位右移(a>>>=n
相当于 a = a>>>n
)
计算value & ~0x7F
分别假设value值为100
、200
100
转二进制为01100100
,200
转二进制为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底层机制简单实现.. |