Protocol Buffer Basics: Dart

acest tutorial oferă o introducere de bază dart programator de a lucra cu tampoane de protocol, folosind versiunea proto3 a limbajului tampoane de protocol. Mergând prin crearea unui exemplu simplu de aplicație, vă arată cum să

  • definiți formatele de mesaje într-un fișier .proto.
  • utilizați compilatorul tampon de protocol.
  • utilizați API-ul tampon Protocol Dart pentru a scrie și citi mesaje.

acesta nu este un ghid cuprinzător pentru utilizarea tampoanelor de protocol în Dart . Pentru informații de referință mai detaliate, consultați Ghidul de limbaj tampon Protocol, turul limbajului Dart, referința API DART, Ghidul codului generat de Dart și referința de codificare.

De ce să folosiți tampoane de protocol?

exemplul pe care îl vom folosi este o aplicație foarte simplă „agendă” care poate citi și scrie detaliile de contact ale oamenilor către și dintr-un fișier. Fiecare persoană din agendă are un nume, un ID, o adresă de e-mail și un număr de telefon de contact.

cum serializați și recuperați date structurate ca aceasta? Există câteva modalități de a rezolva această problemă:

  • puteți inventa o modalitate ad-hoc de a codifica elementele de date într – un singur șir-cum ar fi codificarea 4 ints ca „12:3:-23:67”. Aceasta este o abordare simplă și flexibilă, deși necesită scrierea codării unice și a codului de parsare, iar parsarea impune un cost mic în timpul rulării. Acest lucru funcționează cel mai bine pentru codificarea datelor foarte simple.
  • serializează datele în XML. Această abordare poate fi foarte atractivă, deoarece XML este (un fel de) lizibil uman și există biblioteci obligatorii pentru o mulțime de limbi. Aceasta poate fi o alegere bună dacă doriți să partajați date cu alte aplicații/proiecte. Cu toate acestea, XML este de notorietate spațiu intensiv, și codare/decodare se poate impune o penalizare de performanță uriașă pe aplicații. De asemenea, navigarea într-un arbore XML DOM este considerabil mai complicată decât navigarea câmpurilor simple dintr-o clasă ar fi în mod normal.

tampoanele de Protocol sunt soluția flexibilă, eficientă și automată pentru a rezolva exact această problemă. Cu tampoane de protocol, scrieți o descriere .proto a structurii de date pe care doriți să o stocați. Din aceasta, compilatorul tampon de protocol creează o clasă care implementează codificarea și parsarea automată a datelor tampon de protocol cu un format binar eficient. Clasa generată oferă getters și setteri pentru câmpurile care alcătuiesc un tampon de protocol și are grijă de detaliile de citire și scriere a tamponului de protocol ca unitate. Important, formatul tampon de protocol acceptă ideea extinderii formatului în timp, astfel încât Codul să poată citi în continuare datele codificate cu formatul vechi.

unde se găsește Codul De exemplu

exemplul nostru este un set de linii de comandăaplicații pentru gestionarea unei agende de adresefișier de date, codificat folosind tampoane de protocol.Comanda dart add_person.dart adaugă o nouă intrare în fișierul de date. Comanda dart list_people.dart analizează fișierul de dateși imprimă datele în consolă.

puteți găsi exemplul complet înexemple directoryof depozitul GitHub.

definirea formatului de protocol

pentru a crea aplicația de agendă, va trebui să începeți cu un fișier.proto. Definițiile dintr-un fișier .proto sunt simple: adăugați un mesaj pentru fiecare structură de date pe care doriți să o serializați, apoi specificați un nume și un tip pentru fiecare câmp din mesaj. În exemplul nostru, fișierul.proto care definește mesajele esteaddressbook.proto.

fișierul.proto începe cu o declarație de pachet, care ajută la prevenirea conflictelor de denumire între diferite proiecte.

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

apoi, aveți definițiile mesajului. Un mesaj este doar un agregatconținând un set de câmpuri tastate. Multe tipuri de date simple standard sunt disponibile ca tipuri de câmpuri, inclusiv boolint32floatdouble și string. De asemenea, puteți adăuga o structură suplimentară mesajelor dvs. utilizând alte tipuri de mesaje ca tipuri de câmp.

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;}

în exemplul de mai sus,Person mesajul conținePhoneNumber mesaje, în timp ceAddressBook messagecontainsPerson mesaje. Puteți defini chiar și tipurile de messagenested în interiorul altor mesaje – după cum puteți vedea,PhoneNumber tipul este definit în interiorul Person. De asemenea, puteți defini enum tipuri dacă doriți ca unul dintre câmpurile dvs. să aibă o listă predefinită de valori – aici doriți să specificați că un număr de telefon poate fi unul dintre MOBILEHOME sauWORK.

markerii „= 1”, „= 2” de pe fiecare element identifică „eticheta” unică pe care câmpul o folosește în codificarea binară. Numerele de etichete 1-15 necesită un octet mai puțin pentru a codifica decât numerele mai mari, astfel încât, ca optimizare, puteți decide să utilizați acele etichete pentru elementele utilizate în mod obișnuit sau repetate, lăsând etichetele 16 și mai mari pentru elementele opționale mai puțin utilizate. Fiecare element dintr-un câmp repetat necesită re-codificarea numărului etichetei, astfel încât câmpurile repetate sunt candidați deosebit de buni pentru această optimizare.

dacă o valoare de câmp nu este setată, se folosește valoarea adefault: tipuri numerice zerofor, șirul gol pentru șiruri, Fals pentru bools. Pentru embeddedmessages, valoarea implicită este întotdeauna ” instanța implicită „sau” prototipul ” mesajului, care nu are setat niciunul dintre câmpurile sale. Apelarea accesoriului pentru a obține valoarea unui câmp care nu a fost setat explicit returnează întotdeauna valoarea implicită a câmpului respectiv.

dacă un câmp esterepeated, câmpul poate fi repetat de nenumărate ori (inclusiv zero). Ordinea valorilor repetate va fi păstrată în tamponul protocolului. Gândiți-vă la câmpurile repetate ca matrice de dimensiuni dinamice.

veți găsi un ghid complet pentru scrierea.proto fișiere – inclusiv toate tipurile de câmpuri posibile – în Ghidul de limbaj tampon Protocol. Nu căutați facilități similare cu moștenirea clasei, totuși-tampoanele de protocol nu fac asta.

compilarea tampoanelor de protocol

acum că aveți un .proto, următorul lucru pe care trebuie să-l faceți este să generați clasele de care va trebui să citiți și să scrieți AddressBook (și, prin urmare, Person și PhoneNumber) mesaje. Pentru a face acest lucru, trebuie să rulați compilatorul tampon de protocol protoc pe .proto:

  1. dacă nu ați instalat compilatorul, descărcați pachetul și urmați instrucțiunile din README.
  2. instalați plugin-ul tampon Protocol Dart așa cum este descris în README sale. Executabilulbin/protoc-gen-dart trebuie să fie înPATH pentru tamponul de protocolprotoc pentru a-l găsi.
  3. acum rulați compilatorul, specificând directorul sursă (unde locuiește codul sursă al aplicației dvs. – directorul curent este utilizat dacă nu furnizați o valoare), directorul destinație (unde doriți să meargă codul generat; adesea la fel ca$SRC_DIR) și calea către.proto. În acest caz, ar invoca:
    protoc -I=$SRC_DIR --dart_out=$DST_DIR $SRC_DIR/addressbook.proto

    pentru că doriți Cod Dart, utilizați --dart_out opțiune – opțiuni similare sunt prevăzute pentru alte limbi acceptate.

aceasta genereazăaddressbook.pb.dart în directorul de destinație specificat.

Protocolul tampon API

generatoareaddressbook.pb.dart vă oferă următoarele tipuri utile:

  • unAddressBook clasa cu unList<Person> get people getter.
  • a Person clasa cu metode de acces pentru nameidemail și phones.
  • aPerson_PhoneNumber clasă, cu metode de acces pentrunumber șitype.
  • aPerson_PhoneType clasa cu câmpuri statice pentru fiecare valoare dinPerson.PhoneType enum.

puteți citi mai multe despre detaliile exact ceea ce este generat în Ghidul de cod generat de săgeți.

scrierea unui mesaj

acum să încercăm să folosim clasele tampon de protocol. Primul lucru pe care doriți ca aplicația dvs. de agendă să poată face este să scrieți detalii personale în fișierul dvs. de agendă. Pentru a face acest lucru, trebuie să creați și să populați instanțe ale claselor tampon de protocol și apoi să le scrieți într-un flux de ieșire.

Iată un program care citește unAddressBook dintr-un fișier, adaugă un nouPerson la acesta pe baza datelor introduse de utilizator și scrie noulAddressBook înapoi la Fișier din nou. Sunt evidențiate părțile care apelează direct sau codul de referință generat de compilatorul de protocol.

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());}

citirea unui mesaj

desigur, o agendă nu ar fi de mare folos dacă nu ați putea obține nicio informație din ea! Acest exemplu citește fișierul creat de exemplul de mai sus și imprimă toate informațiile din acesta.

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);}

extinderea unui tampon de Protocol

Mai devreme sau mai târziu,după ce eliberați codul care utilizează tamponul de protocol, veți dori, fără îndoială, să „îmbunătățiți” definiția tamponului de protocol. Dacă doriți ca noile tampoane să fie compatibile înapoi și vechile tampoane să fie compatibile înainte-și aproape sigur doriți acest lucru – atunci există câteva reguli pe care trebuie să le urmați. În noua versiune a theprotocol buffer:

  • nu trebuie să modificați numerele de etichete ale câmpurilor existente.
  • puteți șterge câmpurile.
  • puteți adăuga câmpuri noi, dar trebuie să utilizați numere de etichete proaspete (adică numere de etichete care nu au fost niciodată utilizate în acest tampon de protocol, nici măcar prin câmpuri șterse).

(există unele excepții de laaceste reguli, dar ele sunt rareori folosite.)

dacă urmați aceste reguli, codul vechi va citi Fericit Mesaje noi șipur și simplu ignorați orice câmpuri noi. La vechiul cod, câmpurile singulare care au fosteleted vor avea pur și simplu valoarea lor implicită, iar câmpurile repetate șterse vor fi goale. Noul cod va citi, de asemenea, în mod transparent mesajele vechi.

cu toate acestea,rețineți că câmpurile noi nu vor fi prezente în mesajele vechi, deci va trebui să faceți ceva rezonabil cu valoarea implicită. Atype – specificvaloarea implicită este utilizată: pentru șiruri, valoarea implicită este șirul gol. Pentru booleeni, valoarea implicită este falsă. Pentru tipurile numerice, valoarea implicită este zero.

Lasă un răspuns

Adresa ta de email nu va fi publicată.