proto文件定义
message packageV1 {
int32 id = 1;
string name = 2;
Type type = 3;
enum Type {
DEFAULT = 0;
LOGIN = 1;
SEND = 2;
TO_ALL = 3;
}
}
java创建一个pb对象
PackageV1.packageV1.Builder builder = PackageV1.packageV1.newBuilder();
builder.setId(1);
builder.setName(RandomUtil.randomString(128));//name内容是128英文字母
builder.setType(PackageV1.packageV1.Type.TO_ALL);
PackageV1.packageV1 build = builder.build();
int length = build.toByteArray().length;
log.info("package {} size[{}]",build,length);
return build;
toStrng输出
package id: 1
name: "b2S(C+m+6XAXySdv7D8)qErdAvr~#VrA0j*8NoOm5d5f52xm6$lW5Nd~EBGVFjkdD7UUsULCxgAd&ZVrydMlq3wYOzs~Gjyh+p~ZIi&qO+owwQ$p2)m&+Ss!DnQ#cFs~"
type: TO_ALL
size[135]
也就是说整个pb包的大小是135个字节,其16进制的内容是
080112800129486862316A36256628344F4C594B6232712677294C41584468716577293323265A24422838464B6D7141266D49255A7E36614D5962457223515F67364B36295F434F6D2135294E69714F6369767943364F2644366F65486A76467E325E7233653321744D7A47732633724D6F6A756A6A73235F46425A3249716E75582171301803
而在网络中传输(wireshare抓包或其它网络调式工具观察)的实际字节内容
87 01 08 01 12 80 01 29 48 68 62 31 6A 36 25 66 28 34 4F 4C 59 4B 62 32 71 26 77 29 4C 41 58 44 68 71 65 77 29 33 23 26 5A 24 42 28 38 46 4B 6D 71 41 26 6D 49 25 5A 7E 36 61 4D 59 62 45 72 23 51 5F 67 36 4B 36 29 5F 43 4F 6D 21 35 29 4E 69 71 4F 63 69 76 79 43 36 4F 26 44 36 6F 65 48 6A 76 46 7E 32 5E 72 33 65 33 21 74 4D 7A 47 73 26 33 72 4D 6F 6A 75 6A 6A 73 23 5F 46 42 5A 32 49 71 6E 75 58 21 71 30 18 03
仔细对比可以发现多了87 01 两个字节的内容,这两个字节的内容是什么呢?在netty中使用protobuf编解码的过程中,模板代码如下
ch.pipeline().addLast(new ProtobufVarint32FrameDecoder());
ch.pipeline().addLast(new ProtobufDecoder(PackageV1.packageV1.getDefaultInstance()));
ch.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender());
ch.pipeline().addLast(new ProtobufEncoder());
ch.pipeline().addLast(new IoEventHandler(snowflakeIdWorker));
实际上就是ProtobufVarint32LengthFieldPrependerr的encode方法将整个pb包的二进制 包的大小写在了整个包字节的前面[length] |[data]这种形式,
但是87 01这两个字节是怎么得出135的呢,其实是pb中的变长编码
HEX 87 01 //16进制
BIN 10000111 00000001 //2进制
依据pb变长编码规则
00000001 0000111=128+4+2+1=135
所以在解码的时候需要先读出这个长度值后读取再后续字节才能decode出一个PB包
这个头长度的读取源码见io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder#readRawVarint32
详细代码
//在网络传输的过程中低位在左边
if (!buffer.isReadable()) {
return 0;
}
buffer.markReaderIndex();
byte tmp = buffer.readByte();//读一个字节
if (tmp >= 0) {//如果是正数,说明这个长度的值读取完了,直接返回,(0,127]
return tmp;
} else {//负数,则是不完整的
int result = tmp & 127;//丢字节的msb位
//如果此时没有可度的内容,说明表示长度的数据没有完整发送,重置读指针
if (!buffer.isReadable()) {
buffer.resetReaderIndex();
return 0;
}
//没有后续字节了,按变长数字转化,两个字节的编码
if ((tmp = buffer.readByte()) >= 0) {
result |= tmp << 7;
} else {
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;
}
}