Rechnernetze / Kommunikationssysteme

Protokollpuffer (Protobuf)

Eigenschaften

Wire-Types

Codierung

Beispiel 1

Definition

syntax = "proto3";

message Person {
  int32  id           = 1;              // varint, signed
  string name         = 2;              // length‑delimited
  repeated int32 tags = 3 [packed=true];// packed varint
  bytes  data         = 4;              // raw bytes
}

Instanz

Person p = Person.newBuilder()
        .setId(150)                     // >127 → 2‑Byte‑Varint
        .setName("Ada")
        .addAllTags(Arrays.asList(1, 2, 300))
        .setData(ByteString.copyFrom(new byte[]{0x01,0x02,0x03}))
        .build();

Binäre Darstellung (Hex)

Feld Tag (hex) Payload (hex) Erklärung
id 08 96 01 Tag = (1<<3|0)=0x08, Varint 150 = 0x96 0x01
name 12 03 41 64 61 Tag = (2<<3|2)=0x12, Länge=3, ASCII “Ada”
tags (packed) 1A 04 01 02 AC 02 Tag = (3<<3|2)=0x1A, Länge=4, Varints 1,2,300
data 22 03 01 02 03 Tag = (4<<3|2)=0x22, Länge=3, Roh‑Bytes

Komplette Byte‑Sequenz: 08 96 01 12 03 41 64 61 1A 04 01 02 AC 02 22 03 01 02 03

Decoder

  1. Lese Tag‑Varint0x08 → Feld 1, Wire‑Type 0 → int32.
  2. Lese Payload gemäß Wire‑Type 0 → Varint 0x96 0x01 → 150.
  3. Nächster Tag0x12 → Feld 2, Wire‑Type 2 → length‑delimited.
  4. Lese Länge0x03 → 3 Bytes → “Ada”.
  5. … und so weiter, bis das Ende des Byte‑Arrays erreicht ist.

Beispiel 2

Definition

syntax = "proto3";

package demo;

// ---------- 1. Die innere Nachricht ----------
message Address {
  string street = 1;      // z. B. "Hauptstraße 12"
  string city   = 2;      // z. B. "Berlin"
  int32  zip    = 3;      // z. B. 12345
}

// ---------- 2. Die äußere Nachricht ----------
message Person {
  int32   id          = 1;               // varint
  string  name        = 2;               // length‑delimited
  Address home_address = 3;               // **embedded message**
  repeated PhoneNumber phones = 4;        // repeated embedded message (siehe unten)

  // Noch ein eingebettetes Message‑Beispiel für Wiederholungen
  message PhoneNumber {
    string number = 1;   // z. B. "+49‑30‑123456"
    bool   mobile = 2;   // true → Mobil, false → Festnetz
  }
}

Darstellung

Person p = Person.newBuilder()
        .setId(42)
        .setName("Ada")
        .setHomeAddress(Address.newBuilder()
                .setStreet("Hauptstraße 12")
                .setCity("Berlin")
                .setZip(12345)
                .build())
        .addPhones(Person.PhoneNumber.newBuilder()
                .setNumber("+49-30-123456")
                .setMobile(true)
                .build())
        .addPhones(Person.PhoneNumber.newBuilder()
                .setNumber("+49-30-654321")
                .setMobile(false)
                .build())
        .build();

Tags

Feld (outer) Nummer Wire‑Typ Tag (hex) Erklärung  
id 1 0 (varint) 08 (1 « 3)   0 = 0x08
name 2 2 (length‑delimited) 12 (2 « 3)   2 = 0x12
home_address 3 2 (length‑delimited) 1A (3 « 3)   2 = 0x1A
phones (repeated) 4 2 (length‑delimited) 22 (4 « 3)   2 = 0x22

2.2 Kodierung der inneren Message Address

Feld (inner) Nummer Wire‑Typ Tag (hex) Payload (hex)
street 1 2 0A Länge = 13 → 0D + ASCII "Hauptstraße 12"48 61 75 70 74 73 74 72 61 73 73 65 20 31 32
city 2 2 12 Länge = 6 → 06 + "Berlin"42 65 72 6C 69 6E
zip 3 0 18 Varint = 12345 → B9 60 (0xB9 0x60)

Payload‑Bytes der Address (Tag + Length + Wert‑Sequenz):

0A 0D 48 61 75 70 74 73 74 72 61 73 73 65 20 31 32
12 06 42 65 72 6C 69 6E
18 B9 60

Das sind 23 Bytes.

2.3 Länge‑Feld für home_address

Der äußere Tag 1A wird gefolgt von einer Length‑Varint, die die Größe des gesamten Address‑Payload angibt:

1A 17   // 0x17 = 23 Dezimal

Damit lautet das komplette Segment für home_address:

1A 17
   0A 0D 48 61 75 70 74 73 74 72 61 73 73 65 20 31 32
   12 06 42 65 72 6C 69 6E
   18 B9 60

2.4 Kodierung der repeated Message PhoneNumber

Jedes Element wird einmal als length‑delimited Feld mit Tag 22 kodiert.

2.4.1 Erstes PhoneNumber‑Objekt

Feld Nummer Wire‑Typ Tag Payload
number 1 2 0A Länge = 13 → 0D + "+49-30-123456"2B 34 39 2D 33 30 2D 31 32 33 34 35 36
mobile 2 0 10 Bool = true → Varint = 1 → 01

Payload (inner):

0A 0D 2B 34 39 2D 33 30 2D 31 32 33 34 35 36
10 01

Das sind 19 Bytes.

Äußeres Segment (Tag + Length + Payload):

22 13   // 0x13 = 19 Dezimal
   0A 0D 2B 34 39 2D 33 30 2D 31 32 33 34 35 36
   10 01

2.4.2 Zweites PhoneNumber‑Objekt (mobile = false)

Payload:

0A 0D 2B 34 39 2D 33 30 2D 36 35 34 33 32 31   // "+49-30-654321"
10 00                                          // false → 0

Wieder 19 Bytes, also:

22 13
   0A 0D 2B 34 39 2D 33 30 2D 36 35 34 33 32 31
   10 00

2.5 Vollständige Byte‑Sequenz der äußeren Message Person

Teil Hex‑Bytes
id 08 2A (42 → 0x2A)
name 12 03 41 64 61 (“Ada”)
home_address 1A 17 0A 0D 48 61 75 70 74 73 74 72 61 73 73 65 20 31 32 12 06 42 65 72 6C 69 6E 18 B9 60
phones (1) 22 13 0A 0D 2B 34 39 2D 33 30 2D 31 32 33 34 35 36 10 01
phones (2) 22 13 0A 0D 2B 34 39 2D 33 30 2D 36 35 34 33 32 31 10 00

Komplett (in einer Zeile)

08 2A
12 03 41 64 61
1A 17 0A 0D 48 61 75 70 74 73 74 72 61 73 73 65 20 31 32 12 06 42 65 72 6C 69 6E 18 B9 60
22 13 0A 0D 2B 34 39 2D 33 30 2D 31 32 33 34 35 36 10 01
22 13 0A 0D 2B 34 39 2D 33 30 2D 36 35 34 33 32 31 10 00

Decoder

Ein protobuf‑Decoder arbeitet sequentiell:

  1. Lese Tag‑Varint → bestimme Feld‑Nummer und Wire‑Typ.
  2. Falls Wire‑Typ = 2 (length‑delimited) → lese Length‑Varint, dann die nächsten Length Bytes als Payload.
  3. Interpretieren des Payloads:
    • Wenn das Feld ein embedded message ist, wird der Payload‑Byte‑Stream erneut durch den Decoder geschickt, diesmal mit dem Schema des inneren Message‑Typs.
    • Wenn das Feld ein repeated embedded message ist, wird Schritt 2 für jedes Vorkommen wiederholt (der Decoder erkennt das gleiche Tag‑Byte erneut).

Damit kann ein Decoder beliebig tiefe Verschachtelungen verarbeiten, ohne vorher zu wissen, wie tief die Struktur ist – er folgt einfach den Tags.

Protokoll Buffer mit CRC

Wichtige Punkte:

  1. Serialisiere das Message deterministisch (deterministic=true).
  2. Nimm nur das reine Message‑Byte‑Array (ohne äußere Längen‑Prefixe).
  3. Wähle einen CRC‑Algorithmus (CRC‑32, CRC‑32C, CRC‑64 …) – stelle sicher, dass Sender und Empfänger denselben benutzen.
  4. Berechne die CRC über das Byte‑Array (einfach crc.update(bytes) oder crc32(bytes)).
  5. Transportiere die CRC (z. B. als separates Feld, Trailer, Header oder in einer Wrapper‑Message).
  6. Beim Empfänger:
    • Deserialisiere das Byte‑Array (ohne die CRC‑Bytes).
    • Berechne die CRC erneut und vergleiche mit dem empfangenen Wert.
    • Bei Gleichheit ist das Message‑Payload höchstwahrscheinlich unverändert.

Minimal‑Beispiel (Pseudo‑Code, sprachunabhängig)

function encodeWithCrc(message):
    bytes = protobuf_serialize(message, deterministic=True)
    crc   = crc32c(bytes)               // oder crc32, crc64 …
    return bytes + encode_uint32(crc)    // CRC als 4‑Byte‑Trailer

function decodeWithCrc(blob):
    payload = blob[0 : len(blob)-4]
    received_crc = decode_uint32(blob[-4:])
    if crc32c(payload) != received_crc:
        raise ChecksumError
    return protobuf_parse(payload)

Fragen


Letzte Änderung: 17. September 2025 13:48