denne tutorial giver en grundlæggende Dart programmør introduktion til at arbejde med protocol buffere, ved hjælp af proto3 version af protocol buffere sprog. Ved at gå gennem oprettelse af et simpelt eksempelprogram viser det dig, hvordan du
- definerer meddelelsesformater i en
.proto
fil. - brug protocol buffer compiler.
- brug dart protocol buffer API til at skrive og læse meddelelser.
dette er ikke en omfattende guide til brug af protokolbuffere i Dart . For mere detaljerede referenceoplysninger, se Protocol Buffer Language Guide, Dart Language Tour, Dart API Reference, Dart genereret kode Guide og Kodningsreferencen.
Hvorfor bruge protokolbuffere?
det eksempel, vi skal bruge, er et meget simpelt “adressebog” – program, der kan læse og skrive folks kontaktoplysninger til og fra en fil. Hver person i adressebogen har et navn, et ID, en e-mail-adresse og et kontakttelefonnummer.
hvordan serialiserer og henter du strukturerede data som denne? Der er et par måder at løse dette problem på:
- du kan opfinde en ad hoc måde at kode dataelementerne i en enkelt streng – såsom kodning af 4 ints som “12:3:-23:67”. Dette er en enkel og fleksibel tilgang, selv om det kræver at skrive engangs kodning og parsing kode, og parsing pålægger en lille driftstid omkostninger. Dette fungerer bedst til kodning af meget enkle data.
- serialiser dataene til KML. Denne tilgang kan være meget attraktiv, da HML er (slags) menneskelig læsbar, og der er bindende biblioteker til mange sprog. Dette kan være et godt valg, hvis du vil dele data med andre applikationer/projekter. Men, er notorisk rumintensiv, og kodning / afkodning det kan pålægge en enorm ydeevne straf på applikationer. Det er også betydeligt mere kompliceret at navigere i et DOM-træ end at navigere i enkle felter i en klasse normalt ville være.
Protokolbuffere er den fleksible, effektive, automatiserede løsning til at løse netop dette problem. Med protokolbuffere skriver du en.proto
beskrivelse af den datastruktur, du ønsker at gemme. Fra det opretter protocol buffer compiler en klasse, der implementerer automatisk kodning og parsing af protocol buffer data med et effektivt binært format. Den genererede klasse giver getters og settere for de felter, der udgør en protokolbuffer og tager sig af detaljerne i læsning og skrivning af protokolbufferen som en enhed. Det er vigtigt, at protokolbufferformatet understøtter ideen om at udvide formatet over tid på en sådan måde, at koden stadig kan læse data kodet med det gamle format.
Hvor finder du eksempelkoden
vores eksempel er et sæt kommandolinjeapplikationer til styring af en adressebogdatafil, kodet ved hjælp af protokolbuffere.Kommandoen dart add_person.dart
tilføjer en ny post til datafilen. Kommandoen dart list_people.dart
analyserer datafilenog udskriver dataene til konsollen.
Du kan finde det komplette eksempel ieksempler directoryaf GitHub repository.
definition af dit protokolformat
for at oprette dit adressebogsprogram skal du starte med en.proto
fil. Definitionerne i en.proto
fil ersimpelt: du tilføjer en meddelelse for hver datastruktur, du vilserialisere, angiv derefter et navn og en type for hvert felt i meddelelsen. Ivores eksempel er.proto
filen, der definerer meddelelserne,addressbook.proto
.
.proto
filen starter med en pakkedeklaration, som hjælperat forhindre navnekonflikter mellem forskellige projekter.
syntax = "proto3";package tutorial;import "google/protobuf/timestamp.proto";
Næste, du har din besked definitioner. En besked er bare en aggregatindeholder et sæt indtastede felter. Mange standard simple datatyper er tilgængelige som felttyper, herunder bool
int32
float
double
og string
. Du kan også tilføje yderligere struktur til dine meddelelser ved at bruge andre meddelelsestyper som felttyper.
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 ovenstående eksempel indeholderPerson
meddelelsenPhoneNumber
meddelelser, mensAddressBook
beskedindeholderPerson
meddelelser. Du kan endda definere meddelelsestypernested inde i andre meddelelser-som du kan se, erPhoneNumber
typen defineret inde i Person
. Du kan også definere enum
typer, hvis du vil have et af dine felter til at have enaf en foruddefineret liste over værdier – her vil du angive, at et telefonnummer kan være et af MOBILE
HOME
, ellerWORK
.
markørerne “= 1”, “= 2” på hvert element identificerer det unikke “tag”, som feltet bruger i den binære kodning. Tagnumre 1-15 kræver en mindre byte for at kode end højere tal, så som en optimering kan du beslutte at bruge disse tags til de almindeligt anvendte eller gentagne elementer og efterlade tags 16 og højere for mindre almindeligt anvendte valgfrie elementer. Hvert element i et gentaget felt kræver genkodning af tagnummeret, så gentagne felter er særligt gode kandidater til denne optimering.
hvis en feltværdi ikke er angivet, bruges en standardværdi: nulfor numeriske typer, den tomme streng for strenge, falsk for bools. For indlejrede meddelelser er standardværdien altid” standardinstans “eller” prototype ” af meddelelsen, som ikke har nogen af dens felter indstillet. Hvis du ringer til accessoren for at få værdien af et felt, der ikke er angivet eksplicit, returnerer det altid feltets standardværdi.
Hvis et felt er repeated
, kan feltet gentages et vilkårligt antal gange (inklusive nul). Rækkefølgen af de gentagne værdier bevares i protokolbufferen. Tænk på gentagne felter som dynamisk størrelse arrays.
du finder en komplet guide til skrivning.proto
filer – inklusive alle mulige felttyper – i Protocol Buffer Language Guide. Gå ikke på udkig efter faciliteter, der ligner klassearv, men protokolbuffere gør det ikke.
kompilering af dine protokolbuffere
nu hvor du har en .proto
, er det næste, du skal gøre, at generere de klasser, du skal læse og skrive AddressBook
(og dermed Person
og PhoneNumber
) meddelelser. For at gøre dette skal du køre protocol buffer compiler protoc
på din .proto
:
- hvis du ikke har installeret compileren, skal du hente pakken og følge instruktionerne i README.
- installer dart Protocol Buffer plugin som beskrevet i sin README. Den eksekverbare
bin/protoc-gen-dart
skal være i dinPATH
for protokolbufferenprotoc
for at finde den. - Kør nu kompilatoren og specificer kildekoden (hvor din applikations kildekode lever – den aktuelle mappe bruges, hvis du ikke angiver en værdi), destinationsmappen (hvor du vil have den genererede kode til at gå; ofte det samme som
$SRC_DIR
) og stien til din.proto
. I dette tilfælde vil du påberåbe dig:protoc -I=$SRC_DIR --dart_out=$DST_DIR $SRC_DIR/addressbook.proto
fordi du vil have Dart – kode, bruger du
--dart_out
– lignende muligheder findes for andre understøttede sprog.
dette generereraddressbook.pb.dart
i din angivne destinationsmappe.
protokollen Buffer API
generering addressbook.pb.dart
giver dig følgende nyttige typer:
- An
AddressBook
klasse med enList<Person> get people
getter. - A
Person
klasse med accessormetoder tilname
id
email
ogphones
. - A
Person_PhoneNumber
klasse, med accessormetoder tilnumber
ogtype
. - A
Person_PhoneType
klasse med statiske felter for hver værdi iPerson.PhoneType
enum.
Du kan læse mere om detaljerne i præcis, hvad der genereres i dart Generated Code guide.
skrivning af en besked
lad os nu prøve at bruge dine protokolbufferklasser. Den første ting, du vil have din adressebogsapplikation til at kunne gøre, er at skrive personlige oplysninger til din adressebogsfil. For at gøre dette skal du oprette og udfylde forekomster af dine protokolbufferklasser og derefter skrive dem til en outputstrøm.
Her er et program, der læser en AddressBook
fra en fil, tilføjer en ny Person
til det baseret på brugerinput, og skriver den nye AddressBook
tilbage ud til filen igen. De dele, der direkte kalder eller reference kode genereret af protokollen compiler er fremhævet.
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æsning af en besked
selvfølgelig ville en adressebog ikke være meget brug, hvis du ikke kunne få nogen information ud af det! Dette eksempel læser filen oprettet af ovenstående eksempel og udskriver alle oplysningerne 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);}
udvidelse af en Protokolbuffer
før eller senere efter at du har frigivet koden,der bruger din protokolbuffer, vil du uden tvivl “forbedre” protokolbufferens definition. Hvis du ønsker, at dine nye buffere skal være bagudkompatible, og dine gamle buffere skal være kompatible-og du vil næsten helt sikkert have dette-så er der nogle regler, du skal følge. I den nye version af protocol buffer:
- du må ikke ændre tagnumrene for eksisterende felter.
- du kan slette felter.
- du kan tilføje nye felter, men du skal bruge nye tagnumre (dvs.tagnumre, der aldrig blev brugt i denne protokolbuffer, ikke engang af slettede felter).
(der ernogle undtagelser tildisse regler, men de bruges sjældent.)
Hvis du følger disse regler, vil gammel kode med glæde læse nye meddelelser ogsimpelthen ignorere eventuelle nye felter. Til den gamle kode, ental felter, der varslettet, vil simpelthen have deres standardværdi, og slettede gentagne felter vilvære tomme. Ny kode vil også gennemsigtigt læse gamle meddelelser.
Husk dog, at nye felter ikke vil være til stede i gamle meddelelser,så du bliver nødt til at gøre noget rimeligt med standardværdien. Dentype-specificdefault value bruges: for strenge er standardværdien den tomme streng. For booleans, thedefault værdi er falsk. For numeriske typer er standardværdien nul.