大小端数据存储

概念:

大端模式:是指数据的高字节保存在内存的低地址中,而低子节数据保存在内存的高地址中。
小端模式:是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中。

示例:32 位整数 0x12345678 在内存中从低地址到高地址:

字节序地址递增方向的字节序列
大端12 34 56 78
小端78 56 34 12

在 iOS 上不能把 *(int*) 得到的 4 个字节直接当网络序用,必须用 htonl / 手动按大端拆字节。

Byte 类型(Objective-C / C)

ByteUInt8 的别名,即无符号 8 位整数:

typedef UInt8 Byte;  // UInt8 即 unsigned char

单字节字段应直接赋值,不要对长度为 1 的数组使用 *(int*) 写入(会越界,属于未定义行为)。

新的需求(业务协议):

这是移动端日志数据传给服务器的格式:
{
  "flag": 16,
  "item": {
    "group": "xxxxxx",
    "mirror": 1,
    "items": [
      {
        "flag": 1,
        "key": "log",
        "datas": ["data:application/octet-stream;base64,xxxxxxx"]
      }
    ]
  }
}

datas 里是下面二进制包经 Base64 后的 Data URL。

内层二进制布局

[total_len(4B, 大端)]
[format(4B, ASCII,不足补 0x00)]
[type_len(1B)]
[type_padding_len(1B)]
[data_padding_len(1B)]
[reserved(1B)]
[type + type_padding]      // type 原始 UTF-8,末尾补 0x00 至 4 字节对齐
[data + data_padding]      // data 原始 UTF-8,末尾补 0x00 至 4 字节对齐

字段说明:

字段说明
total_len整个二进制包长度(含 12 字节头),大端
format固定 4 字节,如 "json",不足 4 字节用 0x00 填充,超长则截断
type_lentypeUTF-8 字节长度(不含 padding)
type_padding_lentype 对齐到 4 字节所需的 0x00 个数
data_padding_lendata 对齐到 4 字节所需的 0x00 个数
reserved保留,填 0x00
type / data业务字符串的 UTF-8 原始字节

对齐规则:padding_len = (4 - (len % 4)) % 4;需要 padding 时在末尾追加 0x00不是 ASCII 字符 '0'(0x30)。

total_len 计算:

total_len = 12 + type_len + type_padding_len + data_len + data_padding_len

其中 data_len 为 JSON 字符串的 UTF-8 字节长度(不含 padding)。

疑惑:

之前不知道大小端数据是咋回事,一头雾水。问了安卓同事才勉强明白,我们先了解下Byte,Object-C里面其实是沿用C++的,下面是Byte的定义:
*********************************************************************************/
//Byte是UInt8的别名
typedef UInt8                           Byte;
typedef SInt8                           SignedByte;
typedef wide *                          WidePtr;
typedef UnsignedWide *                  UnsignedWidePtr;
typedef Float80                         extended80;
typedef Float96                         extended96;
typedef SInt8                           VHSelect;
/*********************************************************************************
/********************************************************************************

    Base integer types for all target OS's and CPU's
    
        UInt8            8-bit unsigned integer 
        SInt8            8-bit signed integer
        UInt16          16-bit unsigned integer 
        SInt16          16-bit signed integer           
        UInt32          32-bit unsigned integer 
        SInt32          32-bit signed integer   
        UInt64          64-bit unsigned integer 
        SInt64          64-bit signed integer   

*********************************************************************************/
//UInt8是unsigned char 别名
typedef unsigned char                   UInt8;
typedef signed char                     SInt8;
typedef unsigned short                  UInt16;
typedef signed short                    SInt16;

#if __LP64__
typedef unsigned int                    UInt32;
typedef signed int                      SInt32;
#else
typedef unsigned long                   UInt32;
typedef signed long                     SInt32;
#endif

下面是Objective-C的实现:

工具方法

#import <arpa/inet.h>

/// 向 NSMutableData 追加 32 位无符号整数(大端 / 网络字节序)
static void AppendUInt32BigEndian(uint32_t value, NSMutableData *data) {
    uint32_t be = htonl(value);
    [data appendBytes:&be length:sizeof(be)];
}

/// 计算按 alignment 对齐所需的 padding 字节数
static NSUInteger PaddingLengthForAlignment(NSUInteger length, NSUInteger alignment) {
    NSUInteger remainder = length % alignment;
    return remainder == 0 ? 0 : (alignment - remainder);
}

/// UTF-8 数据 + 末尾 0x00 对齐;返回 padding 长度
static NSData *UTF8DataWithNullPadding(NSString *string,
                                       NSUInteger alignment,
                                       NSUInteger *outByteLength,
                                       NSUInteger *outPaddingLength) {
    NSData *raw = [string dataUsingEncoding:NSUTF8StringEncoding];
    NSUInteger byteLen = raw.length;
    NSUInteger padLen = PaddingLengthForAlignment(byteLen, alignment);

    if (outByteLength) {
        *outByteLength = byteLen;
    }
    if (outPaddingLength) {
        *outPaddingLength = padLen;
    }
    if (padLen == 0) {
        return raw;
    }

    NSMutableData *padded = [raw mutableCopy];
    uint8_t zero = 0;
    for (NSUInteger i = 0; i < padLen; i++) {
        [padded appendBytes:&zero length:1];
    }
    return padded;
}

/// format 固定 4 字节,不足补 0x00
static NSData *FormatFieldData(NSString *format) {
    NSMutableData *field = [NSMutableData dataWithLength:4];
    NSData *raw = [format dataUsingEncoding:NSUTF8StringEncoding];
    NSUInteger copyLen = MIN(raw.length, 4);
    if (copyLen > 0) {
        [field replaceBytesInRange:NSMakeRange(0, copyLen) withBytes:raw.bytes];
    }
    return field;
}
- (NSString *)logByteFill:(NSString *)data format:(NSString *)format type:(NSString *)type {
    if (data.length == 0) {
        return @"";
    }

    NSUInteger typeLen = 0;
    NSUInteger typePaddingLen = 0;
    NSData *typeData = UTF8DataWithNullPadding(type, 4, &typeLen, &typePaddingLen);

    NSUInteger dataLen = 0;
    NSUInteger dataPaddingLen = 0;
    NSData *bodyData = UTF8DataWithNullPadding(data, 4, &dataLen, &dataPaddingLen);

    NSData *formatData = FormatFieldData(format.length > 0 ? format : @"json");

    uint32_t totalLen = (uint32_t)(12 + typeLen + typePaddingLen + dataLen + dataPaddingLen);

    NSMutableData *packet = [NSMutableData dataWithCapacity:totalLen];
    AppendUInt32BigEndian(totalLen, packet);
    [packet appendData:formatData];

    uint8_t headerTail[4];
    headerTail[0] = (uint8_t)MIN(typeLen, UINT8_MAX);
    headerTail[1] = (uint8_t)MIN(typePaddingLen, UINT8_MAX);
    headerTail[2] = (uint8_t)MIN(dataPaddingLen, UINT8_MAX);
    headerTail[3] = 0x00; // reserved
    [packet appendBytes:headerTail length:4];

    [packet appendData:typeData];
    [packet appendData:bodyData];

    NSString *baseString = [packet base64EncodedStringWithOptions:0];
    return [NSString stringWithFormat:@"data:application/octet-stream;base64,%@", baseString];
}

测试:

	//json 33 + cmd + 10 +补位json(3)+cmd(2)+固定长度12=60
	NSString *json = @"{\"fileName\":\"log.ios.frfr4.h264\"}";
    NSString *format = @"json";
    NSString *cmd = @"openStream";
    NSString *data = [self logByteFill:json format:format type:cmd];
    NSLog(@"最后DATA == %@",data);

手工验算(上述示例):

  • type = "openStream" → 10 字节,padding 2 → 12 字节
  • data(json)→ 33 字节,padding 3 → 36 字节
  • total_len = 12 + 10 + 2 + 33 + 3 = 60(0x0000003C,大端:00 00 00 3C
  • format6A 73 6F 6E"json"
  • 头后 4 字节:0A 02 03 00(type_len, type_padding, data_padding, reserved)

可用十六进制查看 packet 与服务器解析结果比对。


Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐