Skip to content

事件Schema与二进制编码练习 ​

前置:能阅读字段编号、整数位操作和 UTF-8 字节长度;Python 3.10+ 用于有限 wire 编码器,区别手写模型与生成代码。教学范围为proto3显式optional;二进制wire;Python3.10+手写有限编码器。核对日期2026-10-17。示例未执行,本轮没有安装依赖、生成协议代码、启动服务或浏览器预览。完整源码和输入文件见下方「同源完整文件」;推演结论只适用于写明的模型。

完整事件schema与编码 ​

完整事件schema与编码用event_id字段1和title标题字段2构造二进制事件,optional note字段3展示未来扩展。手写程序只编码前两字段并跳过支持类型的未知字段,用于看清字节而非替代官方运行库;schema与源码均完整展示。

事件字节预期 ​

事件字节预期在event_id=150、title=A时为十六进制089601120141:08是字段1标签,9601是150的变长编码,12是字段2标签,01是一个字节长度,41是A。长度指字节而非字符,“你”的长度为3。

变长整数逐步练习 ​

变长整数逐步练习拆解300:低七位44,剩余2,因此第一个字节置继续位成为172即ac,第二个字节02。加上字段1标签08得到08ac02;字符串长度如果超过127也需使用变长编码,不能固定写一个长度字节。

同源完整文件 ​

以下README含前置、输入、读者自跑步骤、预期结果、失败、恢复与清理;只做静态核对,没有执行。

event.proto ​

proto
syntax = "proto3";
package events.v1;

message Event {
  uint32 event_id = 1;
  string title = 2;
  optional string note = 3;
  reserved 4, 5;
  reserved "old_type";
}

wire.py ​

python
import json
import sys

def varint(value):
    if type(value) is not int or not 0 <= value <= 0xffffffff:
        raise ValueError('uint32 required')
    output = bytearray()
    while value > 127:
        output.append((value & 127) | 128)
        value >>= 7
    output.append(value)
    return bytes(output)

def read_varint(data, position):
    value = 0
    for shift in range(0, 35, 7):
        if position >= len(data):
            raise ValueError('truncated varint')
        byte = data[position]
        position += 1
        value |= (byte & 127) << shift
        if byte < 128:
            if value > 0xffffffff:
                raise ValueError('uint32 overflow')
            return value, position
    raise ValueError('varint too long')

def encode(identifier, title):
    if not isinstance(title, str):
        raise ValueError('title string required')
    content = title.encode('utf-8')
    if len(content) > 1024:
        raise ValueError('title too large')
    return b'\x08' + varint(identifier) + b'\x12' + varint(len(content)) + content

def decode(data):
    if len(data) > 4096:
        raise ValueError('message too large')
    result, position = {'event_id': 0, 'title': ''}, 0
    while position < len(data):
        tag, position = read_varint(data, position)
        field, wire = tag >> 3, tag & 7
        if field == 0:
            raise ValueError('invalid field zero')
        if field in (1, 2) and wire != {1: 0, 2: 2}[field]:
            raise ValueError('unexpected known-field wire type')
        if wire == 0:
            value, position = read_varint(data, position)
        elif wire == 2:
            length, position = read_varint(data, position)
            if position + length > len(data):
                raise ValueError('truncated payload')
            value = data[position:position + length]
            position += length
        else:
            raise ValueError('unsupported wire type')
        if field == 1:
            result['event_id'] = value
        elif field == 2:
            result['title'] = value.decode('utf-8')
    return result

if __name__ == '__main__':
    try:
        request = json.load(sys.stdin)
        data = bytes.fromhex(request['hex']) if 'hex' in request else encode(request['event_id'], request['title'])
        print(json.dumps({'hex': data.hex(), 'decoded': decode(data)}, ensure_ascii=False))
    except (ValueError, TypeError, KeyError) as error:
        print(str(error), file=sys.stderr)
        sys.exit(2)

requirements.txt ​

text
# Python 3.10+ standard library only; no third-party dependency.

README.md ​

md
# Protocol Buffers数据契约:完整示例

示例未执行。核对日期2026-10-17。教学版本族:proto3显式optional;二进制wire;Python3.10+手写有限编码器。

## 文件与依赖

文件:event.proto, wire.py, requirements.txt。requirements.txt为同源依赖声明;所有代码和配置都随本文展示。wire.py 仅用 Python 3.10+ 标准库;event.proto 是契约对照文件,本轮没有生成代码。

## 输入与前置

前置:理解字段标签、varint、长度前缀和 UTF-8 字节。使用 Python 3.10+ 标准库运行手写 wire.py,不依赖 gRPC 或生成的消息类;event.proto 用于对照契约。输入为编码或解码请求,字段值、字节与错误预期见下节。

读者将来自跑(本轮未执行):在本目录以Python 3.10+读取input.json,PowerShell示意`Get-Content -Raw input.json | python wire.py`;Bash示意`python3 wire.py < input.json`。把下面输入手工保存为UTF-8文件,输出按预期逐项比较。无需安装依赖,没有服务或网络调用;程序只读标准输入并写标准输出,不改原文件。失败信息写标准错误且退出2。恢复是修正输入后重读,清理只删除自己创建的input.json与输出文件,勿递归删除工作目录。

## 预期结果、失败与恢复

event_id=150,title=A对应089601120141;解码同样返回两字段。08ac02对应event_id=300。截断的0896必须拒绝;未知wire类型如0d必须拒绝,而不是假装能完整透传。

失败要保留输入和错误,先与正文的不变量及边界核对。没有真实服务或性能验证,不以这些模型输出证明生产可用性。

## 独立练习

手算ID300、标题“你”的字节,并说明新增optional note对本课解码器有什么影响。

参考答案:ID字段08ac02;标题字段12、长度03、UTF-8e4bda0,因此整体08ac021203e4bda0。note标签1a属于wire2,可按长度跳过;本工具丢弃未知内容,所以不能当无损代理,官方消息对象的未知字段保留策略仍需核对。

先作答,再核对 ​

手算ID300、标题“你”的字节,并说明新增optional note对本课解码器有什么影响。

逐步参考解答

ID字段08ac02;标题字段12、长度03、UTF-8e4bda0,因此整体08ac021203e4bda0。note标签1a属于wire2,可按长度跳过;本工具丢弃未知内容,所以不能当无损代理,官方消息对象的未知字段保留策略仍需核对。

按字节核对事件 ​

同一个示例可以从schema、标签和值三个方向核对。150的低7位是22,继续位使首字节为96;剩余1写为01。标题A的UTF-8长度为1,所以字段2由12、01、41组成。图中按十六进制展示,这不是要求所有消息字段都按固定顺序序列化。

查看图表代码

来源与适用边界 ​

继续阅读