Protocol Buffer:Encoding

本文翻译自Protocol Buffer官方文档: http://code.google.com/apis/protocolbuffers/docs/encoding.html

http://ptsolmyr.com/2010/09/16/protocol-buffer-decode/

Base 128 Varints

要了解ProtoBuf中的编码问题,首先要了解varints。Varints是用一个或多个byte来序列化整数的一种方法。越小的整数占用越少的bytes。

除了最后一个byte,在varint中的每个byte,都把最高有效位(msb)set为1 — 这表明了后面还会跟着其他的bytes。除了最高有效位,剩下的7个bit用来存储该数字以7位为一组的补码形式。最后的有效组在最前面。

例如,对于number 1 — 它只有一位,所以msb没有set为1:

0000 0001

对于300 — 就稍微复杂了:

1010 1100 0000 0010

额滴神啊,怎么才能看出来这是300?首先,把每个byte的msb丢弃,因为它仅仅用来判断是否到达了最后一个byte:

1010 1100 0000 0010
→ 010 1100  000 0010

然后把两组7bit补码反转位置,记得么,varints把最后的significant group存在最前面。然后把它们连接起来,得到最后的值:

000 0010  010 1100
→  000 0010 ++ 010 1100
→  100101100
→  256 + 32 + 8 + 4 = 300

Message Structure

你懂得,一个Protocol Buffer Message实际上是一组Key-Value对。一个message的二进制版本就是用field的名字作为key — 名字和其声明的类型只能在decoding段指定。(也就是在.proto文件中指定)

在一个message被编码后,key和value都被连接在一个byte流中。当message被解码,parser需要能够跳过它不认识的field。这样才能在不破坏旧程序的前提下,在message中添加新的field,否则,旧程序将会因为不认识新field而报错。为了达到这个目的,每个KV对的”key”实际上由两个值决定:.proto文件中的field number,加上能够提供于key相对应的value到底有多长的wire type。

目前可用的wire type如下所示:

TypeMeaningUsed For
0Varintint32, int64, uint32, uint64, sint32, sint64, bool, enum
164-bitfixed64, sfixed64, double
2Length-delimitedstring, bytes, embedded message, packed repeated fields
3Start groupgroups(deprecated)
4End groupgroups(deprecated)
532-bitfixed32, sfixed32, float

在流状message中的每个key都是一个varint,它的值是这么算出来的:(field_number << 3) | wire_type —换句话说,最后三个bit用来存储wire type。

让我们再来看一下简单的例子。现在我们已经知道流中的第一个number永远都是一个varint key了,而且这个数字是0×08:

000 1000

后三个bit是wire type (0), 然后右移3个bit,得到field number (1). 现在真相大白了,tag是1,而且后面跟着的是一个varint。运用我们之前的关于varint-decoding的知识,你就知道下面的两个byte存储的是150。

96 01 = 1001 0110  0000 0001
       → 000 0001  ++  001 0110 (drop the msb and reverse the groups of 7 bits)
       → 10010110
       → 2 + 4 + 16 + 128 = 150

More Value Types

Signed Integers

正如在前面所见到的,所有的和wire type 0相对应的protocol buffer type都被编码成varint。不过,在面对负数的情况中,signed int type(sint32, sint64)和standard int type(int32, int64)还是有很大区别的. 对于int32或int64,如果值为负数,那么生成的varint总是10个byte长度—实际上是把这个负数当作一个很大的unsigned integer处理了。如果你用的是signed type,那么protobuf使用ZigZag编码来生成varint,效率会更高。

ZigZag编码把signed integers映射到unsigned integer,力图使绝对值比较小,这样小负数也会有一个比较小的varint编码值。它的方法是在正数和负数之间曲折的前进,即:-1编码为1,1编为2,-2编为3……

Signed OriginalEncoded As
00
-11
12
-23
21474836474294967294
-21474836484294967295

换句话说,每个值n都按下式编码:

(n << 1) ^ (n >> 31)

如果是64bit,那么用下面的式子:

(n << 1) ^ (n >> 63)

注意,第二个位移运算,也就是(n >> 31)这部分,是一个算术位移,也就是说,位移的结果要么是全0(n是正数),或者全1(n是负数)。

Non-varint Numbers

Non-varint numeric type就简单多了–double和fixed64,wire type是1,告诉parser后面跟着一个64bit的data;float和fixed32,wire type是5,告诉parser后面跟着一个32bit的data。这两种情况下,value都是以little-endian字节序存储的。

Strings

wire type 2(length-delimited)意味着后面跟的value是一个用varint编码的length,然后跟着一个length指定长度的bytes。

message Test2 {
  required string b = 2;
}

如果b被set为“testing”, 那么你会得到下面这个序列:

12 07 74 65 73 74 69 6e 67

标红色的bytes是UTF8编码下的“testing”。这个stream中的key是0×12 –> tag=2, type=2. 表示长度的varint是7,随后跟着7个bytes,就是我们的string。

Embedded Messages

下面定义了一个message,里面又包含了一个Test1类型的message:

message Test3 {
  required Test1 c = 3;
}

下面是编码后的版本

 1a 03 08 96 01

后三个byte正是第一个例子中的结果(08 96 01), 而且他们前面的byte是3(也就是length)。可以看出embedded message的处理方法和string是完全一样的(wiretype = 2).

Optional And Repeated Elements

如果message中包含repeated类型(没有加[packed=true]选项),那么编码后的massage就会存在0个或多个tag值相同的K-V对。这些重复的values并不一定要连续出现;他们中间有可能掺着其他的fields。这些repeated元素之间的顺序会被保存下来,不过不会记录它们中间掺杂的其他field的顺序。

Packed Repeated Fields

在version 2.1.0中,引入了packed repeated fields,声明的时候和repeated fields一样,但是跟着一个特殊的[packed=true]选项。它和普通的repeated fields的编码方式不同。如果一个packed repeated field里面没有任何element,那么它不会出现在encoded message中,只要这个packed repeated field中包含1个或1个以上的element,那么这些element全部被包入一个key-value对中,而且wire type = 2(length-delimited). 每个element的编码方式都和之前讲的一样,但是他们的value前面没有tag。

例如,这样的一个message type:

message Test4 {
  repeated int32 d = 4[packed=true];
}

假设你创建了一个Test4对象,repeated field d中存入了3,270,86942. 那么,编码后的形式将会是:

22        // tag (field number 4, wire type 2)
06        // payload size (6 bytes)
03        // first element (varint 3)
8E 02     // second element (varint 270)
9E A7 05  // third element (varint 86942)

只有类型为原始数字类型的repeated field才能被声明为“packed”。

注意,尽管没有理由把packed repeated field编码成多于一个的key-value对,encoder必须时刻准备着接受multiple key-value pairs。在这种情况下,负荷被累加在了一起。每个pair都一定包含着所有的elements。

Field Order

在.proto文件中,你可以以任意顺序安排field number,但是,序列化一个message时,它的fields应该按照field number的顺序写入。这样可以让负责parse的code可以使用一些依赖于field number顺序的优化技术。当然,protocol buffer的parser必须能够以任意顺序解析field,因为并不是所有的message都是仅仅经过简单的序列化生成的。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
package server import ( "bytes" "encoding/binary" "errors" "time" "github.com/panjf2000/gnet" "github.com/zmicro-team/zmicro/core/log" "github.com/zchat-team/zim/app/conn/protocol" ) type TcpServer struct { gnet.EventHandler addr string codec gnet.ICodec srv *Server } func NewTcpServer(srv *Server, addr string) *TcpServer { ts := new(TcpServer) ts.addr = addr ts.codec = &TcpCodec{} ts.srv = srv return ts } func (s *TcpServer) Start() error { return gnet.Serve(s, s.addr, gnet.WithMulticore(true), gnet.WithTCPKeepAlive(time.Minute*5), gnet.WithCodec(s.codec)) } func (s *TcpServer) Stop() error { //return gnet.Stop(context.Background(), s.addr) return nil } func (s *TcpServer) OnInitComplete(srv gnet.Server) (action gnet.Action) { log.Infof("tcp server is listening on %s (multi-cores: %t, loops: %d)", srv.Addr.String(), srv.Multicore, srv.NumEventLoop) return } func (s *TcpServer) OnOpened(c gnet.Conn) (out []byte, action gnet.Action) { log.Info("TCP OnOpened ...") conn := &Connection{ Status: AuthPending, Conn: c, } c.SetContext(conn) s.srv.OnOpen(conn) return } func (s *TcpServer) OnClosed(c gnet.Conn, err error) (action gnet.Action) { log.Info("TCP OnClose ...") conn, ok := c.Context().(*Connection) if !ok { return } s.srv.OnClose(conn) return } func (s *TcpServer) React(data []byte, c gnet.Conn) (out []byte, action gnet.Action) { conn, ok := c.Context().(*Connection) if !ok { return } s.srv.OnMessage(data, conn) return } // ==================================== Codec ============================================== type TcpCodec struct { } func (_ *TcpCodec) Encode(c gnet.Conn, buf []byte) ([]byte, error) { return buf, nil } func (_ *TcpCodec) Decode(c gnet.Conn) ([]byte, error) { if size, header := c.ReadN(protocol.HeaderLen); size == protocol.HeaderLen { byteBuffer := bytes.NewBuffer(header) var p protocol.Packet if err := binary.Read(byteBuffer, binary.BigEndian, &p.HeaderLen); err != nil { return nil, err } if err := binary.Read(byteBuffer, binary.BigEndian, &p.Version); err != nil { return nil, err } if err := binary.Read(byteBuffer, binary.BigEndian, &p.Cmd); err != nil { return nil, err } if err := binary.Read(byteBuffer, binary.BigEndian, &p.Seq); err != nil { return nil, err } if err := binary.Read(byteBuffer, binary.BigEndian, &p.BodyLen); err != nil { return nil, err } protocolLen := int(protocol.HeaderLen + p.BodyLen) if size, data := c.ReadN(protocolLen); size == protocolLen { c.ShiftN(protocolLen) return data, nil } return nil, errors.New("not enough payload data") } return nil, errors.New("not enough header data") }
06-04

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值