protokoll buffert grunderna: Dart

denna handledning ger en grundläggande Dart programmerare introduktion till att arbeta med protokoll buffertar, med proto3 versionen av protokollet buffertar språk. Genom att gå igenom att skapa ett enkelt exempelapplikation visar det dig hur du

  • definierar meddelandeformat i en.proto – fil.
  • använd protokollbuffertkompilatorn.
  • använd Dart protocol buffer API för att skriva och läsa meddelanden.

detta är inte en omfattande guide för att använda protokollbuffertar i Dart . För mer detaljerad referensinformation, se Protokollbuffertspråksguiden, Dart Language Tour, Dart API-referensen, Dart Generated Code Guide och Kodningsreferensen.

Varför använda protokollbuffertar?

exemplet vi ska använda är en mycket enkel” adressbok ” – applikation som kan läsa och skriva människors kontaktuppgifter till och från en fil. Varje person i adressboken har ett namn, ett ID, en e-postadress och ett kontakttelefonnummer.

hur serialiserar och hämtar du strukturerad data så här? Det finns några sätt att lösa detta problem:

  • Du kan uppfinna ett ad hoc-sätt att koda dataobjekten i en enda sträng – till exempel kodning av 4 ints som ”12:3:-23:67”. Detta är ett enkelt och flexibelt tillvägagångssätt, även om det kräver att man skriver engångskodning och parsningskod, och parsningen medför en liten körtidskostnad. Detta fungerar bäst för kodning av mycket enkla data.
  • Serialisera data till XML. Detta tillvägagångssätt kan vara mycket attraktivt eftersom XML är (typ av) läsbar och det finns bindande bibliotek för många språk. Detta kan vara ett bra val om du vill dela data med andra applikationer/projekt. XML är dock notoriskt rymdintensivt, och kodning/avkodning kan medföra en enorm prestandastraff på applikationer. Att navigera i ett XML DOM-träd är också betydligt mer komplicerat än att navigera i enkla fält i en klass normalt skulle vara.

Protokollbuffertar är den flexibla, effektiva, automatiserade lösningen för att lösa exakt detta problem. Med protokollbuffertar skriver du en.proto beskrivning av den datastruktur du vill lagra. Från det skapar protokollbuffertkompilatorn en klass som implementerar automatisk kodning och tolkning av protokollbuffertdata med ett effektivt binärt format. Den genererade klassen tillhandahåller getters och setters för de fält som utgör en protokollbuffert och tar hand om detaljerna för att läsa och skriva protokollbufferten som en enhet. Det är viktigt att protokollbuffertformatet stöder tanken på att utvidga formatet över tiden på ett sådant sätt att koden fortfarande kan läsa data kodade med det gamla formatet.

var hittar du exempelkoden

vårt exempel är en uppsättning kommandoradsapplikationer för hantering av en adressbokdatafil, kodad med protokollbuffertar.Kommandot dart add_person.dart lägger till en ny post i datafilen. Kommandot dart list_people.dart tolkar datafilenoch skriver ut data till konsolen.

Du kan hitta det fullständiga exemplet iexempeldirektoryav GitHub-förvaret.

definiera ditt protokollformat

för att skapa din adressboksprogram måste du börja med en.proto fil. Definitionerna i en.proto fil ärenkelt: du lägger till ett meddelande för varje datastruktur du vill serialisera, ange sedan ett namn och en typ för varje fält i meddelandet. I vårt exempel är filen .proto som definierar meddelandenaaddressbook.proto.

filen.proto börjar med en paketdeklaration som hjälper till att förhindra namnkonflikter mellan olika projekt.

syntax = "proto3";package tutorial;import "google/protobuf/timestamp.proto";

därefter har du dina meddelandedefinitioner. Ett meddelande är bara ett aggregatinnehållande en uppsättning typade fält. Många vanliga enkla datatyper finns som fälttyper, inklusive boolint32floatdouble och string. Du kan också lägga till ytterligare struktur i dina meddelanden genom att använda andra meddelandetyper som fälttyper.

message Person { string name = 1; int32 id = 2; // Unique ID number for this person. string email = 3; enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } message PhoneNumber { string number = 1; PhoneType type = 2; } repeated PhoneNumber phones = 4; google.protobuf.Timestamp last_updated = 5;}// Our address book file is just one of these.message AddressBook { repeated Person people = 1;}

i exemplet ovan innehåller meddelandetPersonPhoneNumber, medanAddressBook messagecontainsPerson meddelanden. Du kan även definiera meddelandetypernested inuti andra meddelanden-som du kan se ärPhoneNumber typ definierad inuti Person. Du kan också definiera enum typer om du vill att ett av dina fält ska ha en av en fördefinierad lista med värden – Här Vill du ange att ett telefonnummer kan vara en av MOBILEHOME, ellerWORK.

markörerna ”= 1”, ”= 2” på varje element identifierar den unika ”taggen” som fältet använder i den binära kodningen. Taggnummer 1-15 kräver en mindre byte för att koda än högre siffror, så som en optimering kan du välja att använda dessa taggar för de vanliga eller upprepade elementen och lämna taggar 16 och högre för mindre vanliga valfria element. Varje element i ett upprepat fält kräver omkodning av taggnumret, så upprepade fält är särskilt bra kandidater för denna optimering.

om ett fältvärde inte är inställt används adefault-värde: zerofor numeriska typer, den tomma strängen för strängar, false för bools. För embeddedmessages är standardvärdet alltid” standardinstans ”eller” prototyp ” av meddelandet, som inte har något av dess fält. Anropar accessorn för att få värdet av ett fält som inte uttryckligen har angetts returnerar alltid det fältets standardvärde.

om ett fält är repeated kan fältet upprepas valfritt antal gånger (inklusive noll). Ordningen för de upprepade värdena kommer att bevaras i protokollbufferten. Tänk på upprepade fält som dynamiska matriser.

Du hittar en komplett guide för att skriva .proto filer – inklusive alla möjliga fälttyper – i Protokollbuffertens språkguide. Leta inte efter anläggningar som liknar klassarv – men protokollbuffertar gör det inte.

kompilera dina protokollbuffertar

Nu när du har ett .proto, nästa sak du behöver göra är att generera de klasser du behöver läsa och skriva AddressBook (och därmed Person och PhoneNumber) meddelanden. För att göra detta måste du köra protokollbuffertkompilatorn protoc på din .proto:

  1. Om du inte har installerat kompilatorn, ladda ner paketet och följ instruktionerna i README.
  2. installera plugin-programmet Dart Protocol Buffer enligt beskrivningen i dess README. Den körbara bin/protoc-gen-dart måste finnas i ditt PATH för protokollbufferten protoc för att hitta den.
  3. kör nu kompilatorn och ange källkatalogen (där programmets källkod bor – den aktuella katalogen används om du inte anger ett värde), destinationskatalogen (där du vill att den genererade koden ska gå; ofta samma som $SRC_DIR) och sökvägen till ditt .proto. I det här fallet skulle du åberopa:
    protoc -I=$SRC_DIR --dart_out=$DST_DIR $SRC_DIR/addressbook.proto

    eftersom du vill ha Dart – kod använder du alternativet--dart_out – liknande alternativ tillhandahålls för andra språk som stöds.

detta genererar addressbook.pb.dart I din angivna destinationskatalog.

protokollet buffert API

generera addressbook.pb.dart ger dig följande användbara typer:

  • en AddressBook klass med en List<Person> get people getter.
  • aPerson klass med accessormetoder förnameidemail ochphones.
  • aPerson_PhoneNumber klass, med accessormetoder förnumber ochtype.
  • aPerson_PhoneType klass med statiska fält för varje värde iPerson.PhoneType enum.

Du kan läsa mer om detaljerna om exakt vad som genereras i Dart Generated Code guide.

skriva ett meddelande

låt oss nu försöka använda dina protokollbuffertklasser. Det första du vill att din adressbok ansökan för att kunna göra är att skriva personuppgifter till din adressbok fil. För att göra detta måste du skapa och fylla i instanser av dina protokollbuffertklasser och sedan skriva dem till en utgångsström.

Här är ett program som läser ett AddressBook från en fil, lägger till ett nytt Person till det baserat på användarinmatning och skriver det nya AddressBook tillbaka till filen igen. De delar som direkt anropar eller referenskod som genereras av protokollkompilatorn markeras.

import 'dart:io';import 'dart_tutorial/addressbook.pb.dart';// This function fills in a Person message based on user input.Person promtForAddress() { Person person = Person(); print('Enter person ID: '); String input = stdin.readLineSync(); person.id = int.parse(input); print('Enter name'); person.name = stdin.readLineSync(); print('Enter email address (blank for none) : '); String email = stdin.readLineSync(); if (email.isNotEmpty) { person.email = email; } while (true) { print('Enter a phone number (or leave blank to finish): '); String number = stdin.readLineSync(); if (number.isEmpty) break; Person_PhoneNumber phoneNumber = Person_PhoneNumber(); phoneNumber.number = number; print('Is this a mobile, home, or work phone? '); String type = stdin.readLineSync(); switch (type) { case 'mobile': phoneNumber.type = Person_PhoneType.MOBILE; break; case 'home': phoneNumber.type = Person_PhoneType.HOME; break; case 'work': phoneNumber.type = Person_PhoneType.WORK; break; default: print('Unknown phone type. Using default.'); } person.phones.add(phoneNumber); } return person;}// Reads the entire address book from a file, adds one person based// on user input, then writes it back out to the same file.main(List arguments) { if (arguments.length != 1) { print('Usage: add_person ADDRESS_BOOK_FILE'); exit(-1); } File file = File(arguments.first); AddressBook addressBook; if (!file.existsSync()) { print('File not found. Creating new file.'); addressBook = AddressBook(); } else { addressBook = AddressBook.fromBuffer(file.readAsBytesSync()); } addressBook.people.add(promtForAddress()); file.writeAsBytes(addressBook.writeToBuffer());}

läser ett meddelande

naturligtvis skulle en adressbok inte vara mycket användbar om du inte kunde få någon information ur den! Det här exemplet läser filen som skapats av ovanstående exempel och skriver ut all information i den.

import 'dart:io';import 'dart_tutorial/addressbook.pb.dart';import 'dart_tutorial/addressbook.pbenum.dart';// Iterates though all people in the AddressBook and prints info about them.void printAddressBook(AddressBook addressBook) { for (Person person in addressBook.people) { print('Person ID: ${ person.id}'); print(' Name: ${ person.name}'); if (person.hasEmail()) { print(' E-mail address:${ person.email}'); } for (Person_PhoneNumber phoneNumber in person.phones) { switch (phoneNumber.type) { case Person_PhoneType.MOBILE: print(' Mobile phone #: '); break; case Person_PhoneType.HOME: print(' Home phone #: '); break; case Person_PhoneType.WORK: print(' Work phone #: '); break; default: print(' Unknown phone #: '); break; } print(phoneNumber.number); } }}// Reads the entire address book from a file and prints all// the information inside.main(List arguments) { if (arguments.length != 1) { print('Usage: list_person ADDRESS_BOOK_FILE'); exit(-1); } // Read the existing address book. File file = new File(arguments.first); AddressBook addressBook = new AddressBook.fromBuffer(file.readAsBytesSync()); printAddressBook(addressBook);}

utöka en Protokollbuffert

förr eller senare efter att du släppt koden som använder din protokollbuffert,kommer du utan tvekan att vilja” förbättra ” protokollbuffertens definition. Om du vill att dina nya buffertar ska vara bakåtkompatibla och dina gamla buffertar ska vara framåtkompatibla-och du vill nästan säkert ha det här – då finns det några regler du måste följa. I den nya versionen avprotokollbuffert:

  • du får inte ändra taggnumren för befintliga fält.
  • Du kan ta bort fält.
  • Du kan lägga till nya fält men du måste använda nya taggnummer (dvs. taggnummer som aldrig användes i denna protokollbuffert, inte ens av raderade fält).

( det finnsnågra undantag tillDessa regler, men de används sällan.)

om du följer dessa regler, gammal kod kommer gärna läsa nya meddelanden ochhelt enkelt ignorera alla nya fält. Till den gamla koden, singulära fält som weredeleted kommer helt enkelt att ha sitt standardvärde, och raderade upprepade fält kommer att vara tomma. Ny kod läser också öppet gamla meddelanden.

tänk dock på att nya fält inte kommer att finnas i gamla meddelanden,så du måste göra något rimligt med standardvärdet. Atype-specificdefault valueanvänds: för strängar är standardvärdet den tomma strängen. För booleaner är standardvärdet falskt. För numeriska typer är standardvärdet noll.

Lämna ett svar

Din e-postadress kommer inte publiceras.