美文网首页ProtoBuf
protobuf3 从object-c角度学习数据编码

protobuf3 从object-c角度学习数据编码

作者: 困惑困惑困惑 | 来源:发表于2018-03-13 19:03 被阅读19次

    原文链接:http://blog.csdn.net/taishanduba/article/details/57177165

    一.protobuf编码类型

    protobuf.一共有6中编码方式,其中group目前已不建议使用.

    * WireVarint     = 0  // int32, int64, uint32,
    * uint64, ,sint32 sint64, bool, enum. 变长,1-10个字节,用VARINT编码且压缩
    * WireFixed64    = 1  // fixed64, sfixed64, double . 固定8个字节
    * WireBytes      = 2  // string, bytes, embedded messages, packed repeated fields. 变长,在key后会跟一个编码过的长度字段
    *WireStartGroup = 3  // 一个组(该组可以是嵌套类型,也可以是repeated类型)的开始标志。deprecated
    *WireEndGroup   = 4  // 一个组(该组可以是嵌套类型,也可以是repeated类型)的结束标志。deprecated
    *WireFixed32    = 5  // fixed32, sfixed32, float. 固定4个字节
    

    二.编码过程

    protobud每条消息(message)都是有一系列的key-value对组成的, key和value分别采用不同的编码方式。对某一条件消息(message)进行编码的时候,是把该消息中所有的key-value对序列化成二进制字节流;而解码的时候,解码程序读入二进制的字节流,解析出每一个key-value对,如果解码过程中遇到识别不出来的类型,直接跳过。这样的机制,保证了即使该消息添加了新的字段,也不会影响旧的编/解码程序正常工作。key由两部分组成,一部分是在定义消息时对字段的编号(field_num),另一部分是字段类型(wire_type)
    三编码方法

    1.可变数据类型

    下面是可变32位正数的编码方法,其他64位,32位无符号数据原理相同

    //1.小于获取等于127则直接写入,头部为0(128才可能为1)
    //2.大于127,则先获取小于127的部分小端写入,小于127的数与128或相当于头部加1,逻辑右移继续该循环
    static void GPBWriteRawVarint32(GPBOutputBufferState *state, int32_t value) {
      while (YES) {
        if ((value & ~0x7F) == 0) {// 判断value是否大于127
          uint8_t val = (uint8_t)value;
          GPBWriteRawByte(state, val);
          return;
        } else {
            GPBWriteRawByte(state, (value & 0x7F) | 0x80);  //大于127,则取小于127的数与128或相当于头部加1
          value = GPBLogicalRightShift32(value, 7); //右移7位
        }
      }
    }
    

    从代码中已经看的很明白了.如果以1开头,则表示和上一个是同一个数据块;如果以0开头则表示是一个独立数据块.谷歌采用的是小端排序.这种可变编码方式大大减少了无用代码的传输.如果数据都是很小的数据,则可以节省空间.value的key都是采用这种方式编码

    key的生成方式.创建tag,左移tag的位数(3位),然后将wireType放在后面,wireType最大是5最多3位.通过这种方式将数据编号和数据类型编码到了一起

    uint32_t GPBWireFormatMakeTag(uint32_t fieldNumber, GPBWireFormat wireType) {
      return (fieldNumber << GPBWireFormatTagTypeBits) | wireType;
    }
    

    2.Zigzag编码方式

    sint32 sint64使用Zigzag的编码方式.关于这种方式可以参考链接,基本原理是将符号位放到最后一位,如果是负数则剩下的位数取反.接着采用可变编码的方式编码.示例如下

    GPB_INLINE uint32_t GPBEncodeZigZag32(int32_t n) {
      //1.算数左移
      //2.算数右移31位
      //3.两者异或达到取反的目的
      return (uint32_t)((n << 1) ^ (n >> 31));
    }
    

    3.Bytes编码类型

    Bytes类型的编码很简单: 长度 + 数据字节. 注意长度字段使用的也是varint编码.以string的写入为例

    //1.写入tag和以上相同
    //2.写入value时加上了长度
    - (void)writeString:(int32_t)fieldNumber value:(NSString *)value {
      GPBWriteTagWithFormat(&state_, fieldNumber, GPBWireFormatLengthDelimited);
      [self writeStringNoTag:value];
    }
    
    //写入字符串
    - (void)writeStringNoTag:(const NSString *)value {
      size_t length = [value lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
        //加上str长度
      GPBWriteRawVarint32(&state_, (int32_t)length);
      if (length == 0) {
        return;
      }
    const char *quickString =
          CFStringGetCStringPtr((CFStringRef)value, kCFStringEncodingUTF8);
    
      // Fast path: Most strings are short, if the buffer already has space,
      // add to it directly.
      NSUInteger bufferBytesLeft = state_.size - state_.position;
      if (bufferBytesLeft >= length) {
    
          //还有空间
        NSUInteger usedBufferLength = 0;
        BOOL result;
        if (quickString != NULL) {
            //des,src,len
          memcpy(state_.bytes + state_.position, quickString, length);
          usedBufferLength = length;
          result = YES;
        } else {
          result = [value getBytes:state_.bytes + state_.position
                         maxLength:bufferBytesLeft
                        usedLength:&usedBufferLength
                          encoding:NSUTF8StringEncoding
                           options:(NSStringEncodingConversionOptions)0
                             range:NSMakeRange(0, [value length])
                    remainingRange:NULL];
        }
    if (result) {
          NSAssert2((usedBufferLength == length),
                    @"Our UTF8 calc was wrong? %tu vs %zd", usedBufferLength,
                    length);
          state_.position += usedBufferLength;
          return;
        }
      } else if (quickString != NULL) {
        [self writeRawPtr:quickString offset:0 length:length];
      } else {
        // Slow path: just get it as data and write it out.
        NSData *utf8Data = [value dataUsingEncoding:NSUTF8StringEncoding];
        NSAssert2(([utf8Data length] == length),
                  @"Strings UTF8 length was wrong? %tu vs %zd", [utf8Data length],
                  length);
        [self writeRawData:utf8Data];
      }
    }
    

    4.WireFixed32类型编码

    编码长度是是不可变,32位就是32位.64位与double的原理相同

    static void GPBWriteRawLittleEndian32(GPBOutputBufferState *state,
                                          int32_t value) {
        //8个bit的写,写4次,32位
      GPBWriteRawByte(state, (value)&0xFF);//写入后两位
      GPBWriteRawByte(state, (value >> 8) & 0xFF);
      GPBWriteRawByte(state, (value >> 16) & 0xFF);
      GPBWriteRawByte(state, (value >> 24) & 0xFF);
    }
    

    5.repeated类型字段编码

    repeated在object-c中的展现方式是数组,而且数组中每一个元素的类型都是相同的.repeated编码会首先写入tag,然后加上长度,最后遍历添加每一个item对应的编码数据Int32Array代码如下,其他类似

    - (void)writeInt32Array:(int32_t)fieldNumber
                     values:(GPBInt32Array *)values
                        tag:(uint32_t)tag {
      if (tag != 0) {
        if (values.count == 0) return;
        __block size_t dataSize = 0;
    
          //计算数据大小
        [values enumerateValuesWithBlock:^(int32_t value, NSUInteger idx, BOOL *stop) {
    #pragma unused(idx, stop)
          dataSize += GPBComputeInt32SizeNoTag(value);
        }];
    
          //写入tag
        GPBWriteRawVarint32(&state_, tag);
          //写入大小
        GPBWriteRawVarint32(&state_, (int32_t)dataSize);
    
        [values enumerateValuesWithBlock:^(int32_t value, NSUInteger idx, BOOL *stop) {
    #pragma unused(idx, stop)
          [self writeInt32NoTag:value];
        }];
      } else {
        [values enumerateValuesWithBlock:^(int32_t value, NSUInteger idx, BOOL *stop) {
    #pragma unused(idx, stop)
          [self writeInt32:fieldNumber value:value];
        }];
      }
    }
    

    四.数据类型判定

    1.proto文件转objc的过程中,protobuf工具已经将对应的int,string等转换成合适的数据类型

    2.具体类型会在类的descriptor记录,对应关系如下面代码

    GPBWireFormat GPBWireFormatForType(GPBDataType type, BOOL isPacked) {
      if (isPacked) {
        return GPBWireFormatLengthDelimited;
      }
    
      static const GPBWireFormat format[GPBDataType_Count] = {
          GPBWireFormatVarint,           // GPBDataTypeBool
          GPBWireFormatFixed32,          // GPBDataTypeFixed32
          GPBWireFormatFixed32,          // GPBDataTypeSFixed32
          GPBWireFormatFixed32,          // GPBDataTypeFloat
          GPBWireFormatFixed64,          // GPBDataTypeFixed64
          GPBWireFormatFixed64,          // GPBDataTypeSFixed64
          GPBWireFormatFixed64,          // GPBDataTypeDouble
          GPBWireFormatVarint,           // GPBDataTypeInt32
          GPBWireFormatVarint,           // GPBDataTypeInt64
          GPBWireFormatVarint,           // GPBDataTypeSInt32
          GPBWireFormatVarint,           // GPBDataTypeSInt64
          GPBWireFormatVarint,           // GPBDataTypeUInt32
          GPBWireFormatVarint,           // GPBDataTypeUInt64
          GPBWireFormatLengthDelimited,  // GPBDataTypeBytes
          GPBWireFormatLengthDelimited,  // GPBDataTypeString
          GPBWireFormatLengthDelimited,  // GPBDataTypeMessage
          GPBWireFormatStartGroup,       // GPBDataTypeGroup
          GPBWireFormatVarint            // GPBDataTypeEnum
      };
      return format[type];
    }
    

    相关文章

      网友评论

        本文标题:protobuf3 从object-c角度学习数据编码

        本文链接:https://www.haomeiwen.com/subject/dthtqftx.html