podstawy bufora protokołu: Dart

Ten poradnik zawiera podstawowe wprowadzenie programisty Darta do pracy z buforami protokołu, przy użyciu wersji proto3 języka buforów protokołu. Przechodząc przez tworzenie prostej przykładowej aplikacji, pokazuje, jak

  • zdefiniować formaty wiadomości w pliku .proto.
  • użyj kompilatora bufora protokołu.
  • Użyj interfejsu API bufora protokołu Dart do zapisu i odczytu wiadomości.

nie jest to wyczerpujący przewodnik po używaniu buforów protokołów w Dart . Aby uzyskać więcej szczegółowych informacji, zobacz Przewodnik po języku bufora protokołu, Przewodnik po języku Dart, odniesienie do API Dart, Przewodnik po kodzie generowanym przez Dart i odniesienie do kodowania.

po co używać buforów protokołu?

przykład, którego użyjemy, to bardzo prosta aplikacja „Książka Adresowa”, która może odczytywać i zapisywać dane kontaktowe osób do i z pliku. Każda osoba w książce adresowej ma imię i nazwisko, identyfikator, adres e-mail i numer telefonu kontaktowego.

jak serializować i pobierać takie ustrukturyzowane dane? Istnieje kilka sposobów na rozwiązanie tego problemu:

  • możesz wymyślić ad-hoc sposób kodowania elementów danych w jednym łańcuchu-na przykład kodowanie 4 ints jako „12:3:-23:67”. Jest to proste i elastyczne podejście, chociaż wymaga pisania jednorazowego kodowania i parsowania kodu, a parsowanie nakłada niewielki koszt w czasie pracy. Działa to najlepiej do kodowania bardzo prostych danych.
  • serializacja danych do XML. Takie podejście może być bardzo atrakcyjne, ponieważ XML jest (swego rodzaju) czytelny dla człowieka i istnieją wiążące biblioteki dla wielu języków. Może to być dobry wybór, jeśli chcesz udostępniać dane innym aplikacjom/projektom. Jednak XML notorycznie zajmuje dużo miejsca, a jego kodowanie/dekodowanie może nakładać ogromne kary za wydajność na aplikacje. Ponadto poruszanie się po drzewie DOM XML jest znacznie bardziej skomplikowane niż poruszanie się po prostych polach w klasie.

bufory protokołów są elastycznym, wydajnym, zautomatyzowanym rozwiązaniem, które rozwiązuje dokładnie ten problem. Za pomocą buforów protokołów piszesz.proto opis struktury danych, którą chcesz przechowywać. Z tego, kompilator bufora protokołu tworzy klasę, która implementuje automatyczne kodowanie i parsowanie danych bufora protokołu za pomocą wydajnego formatu binarnego. Wygenerowana Klasa dostarcza gettery i settery dla pól tworzących bufor protokołu i dba o szczegóły odczytu i zapisu bufora protokołu jako jednostki. Co ważne, format bufora protokołu wspiera ideę rozszerzenia formatu w czasie w taki sposób, aby Kod mógł nadal odczytywać dane zakodowane starym formatem.

gdzie znaleźć przykładowy kod

Nasz przykład jest zestawem poleceń-lineaplikacji do zarządzania plikiem danych książki adresowej, zakodowanym przy użyciu buforów protokołów.Polecenie dart add_person.dart dodaje nowy wpis do pliku danych. Polecenie dart list_people.dart przetwarza plik danych i wypisuje dane do konsoli.

kompletny przykład znajdziesz w katalogu próbek repozytorium GitHub.

Definiowanie formatu protokołu

aby utworzyć aplikację książki adresowej, musisz zacząć od Pliku.proto. Definicje w pliku.proto są proste: dodajesz wiadomość dla każdej struktury danych, którą chcesz zserializować, a następnie określasz nazwę i typ dla każdego pola w wiadomości. W naszym przykładzie plik.proto, który definiuje wiadomości, toaddressbook.proto.

plik.proto rozpoczyna się od deklaracji pakietów, która pomaga zapobiegać konfliktom nazw między różnymi projektami.

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

następnie masz swoje definicje wiadomości. Wiadomość jest tylko agregatemzawiera zestaw wpisanych pól. Wiele standardowych prostych typów danych jest dostępnych jako typy pól, w tym boolint32floatdouble I stringdiv>. Możesz także dodać do wiadomości dalszą strukturę, używając innych typów wiadomości jako typów pól.

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

w powyższym przykładzie wiadomośćPerson zawiera wiadomościPhoneNumber, podczas gdy wiadomośćAddressBook zawieraPerson wiadomości. Można nawet zdefiniować typy wiadomościnested wewnątrz innych wiadomości – jak widać,PhoneNumber typ jest zdefiniowany wewnątrzPerson. Możesz również zdefiniować typy enum, jeśli chcesz, aby jedno z twoich pól miało predefiniowaną listę wartości – tutaj chcesz określić, że numer telefonu może być jednym z MOBILEHOME lubWORK.

znaczniki „= 1”, „= 2” na każdym elemencie identyfikują unikalny „tag”, którego pole używa w kodowaniu binarnym. Numery znaczników 1-15 wymagają o jeden bajt mniej do zakodowania niż liczby wyższe, więc jako optymalizację możesz zdecydować się na użycie tych znaczników dla powszechnie używanych lub powtarzających się elementów, pozostawiając znaczniki 16 i wyższe dla rzadziej używanych elementów opcjonalnych. Każdy element w powtarzającym się polu wymaga ponownego kodowania numeru znacznika, więc powtarzające się pola są szczególnie dobrymi kandydatami do tej optymalizacji.

Jeśli wartość pola nie jest ustawiona, używana jest wartość adefault: zerofor typy liczbowe, pusty łańcuch dla łańcuchów, false dla Booli. W przypadku embeddedmessages wartością domyślną jest zawsze „instancja domyślna” lub „prototyp” wiadomości, która nie ma ustawionych pól. Wywołanie accessora w celu uzyskania wartości pola, które nie zostało jawnie ustawione, zawsze zwraca wartość domyślną tego pola.

Jeśli pole jest repeated, pole może być powtórzone dowolną liczbę razy (w tym zero). Kolejność powtarzanych wartości zostanie zachowana w buforze protokołu. Pomyśl o powtarzających się polach jako o tablicach o dynamicznych rozmiarach.

w podręczniku języka bufora protokołu znajdziesz kompletny przewodnik pisania.proto plików – w tym wszystkich możliwych typów pól. Nie szukaj jednak obiektów podobnych do dziedziczenia klas – bufory protokołów tego nie robią.

Kompilowanie buforów protokołu

teraz, gdy masz.proto, następną rzeczą, którą musisz zrobić, to wygenerować klasy, które będziesz musiał odczytać i zapisaćAddressBook (I stądPerson IPhoneNumber) wiadomości. Aby to zrobić, musisz uruchomić kompilator bufora protokołu protoc na swoim .proto:

  1. jeśli nie zainstalowałeś kompilatora, pobierz pakiet i postępuj zgodnie z instrukcjami w README.
  2. Zainstaluj wtyczkę bufora protokołu Dart zgodnie z opisem w README. Plik wykonywalnybin/protoc-gen-dart musi znajdować się w TwoimPATH dla bufora protokołuprotoc, aby go znaleźć.
  3. Uruchom teraz kompilator, określając katalog źródłowy (w którym znajduje się kod źródłowy Twojej aplikacji – bieżący katalog jest używany, jeśli nie podasz wartości), katalog docelowy (do którego ma trafić wygenerowany kod; często taki sam jak $SRC_DIR) oraz ścieżkę do twojego .proto. W takim przypadku wywołujesz:
    protoc -I=$SRC_DIR --dart_out=$DST_DIR $SRC_DIR/addressbook.proto

    ponieważ chcesz Kod Dart, użyj opcji--dart_out – podobne opcje są dostępne dla innych obsługiwanych języków.

to generujeaddressbook.pb.dart w podanym katalogu docelowym.

API bufora protokołu

generowanieaddressbook.pb.dart daje następujące użyteczne typy:

  • AnAddressBook klasa zList<Person> get people getter.
  • a Person klasa z metodami akcesorowymi dla nameidemail I phones.
  • aPerson_PhoneNumber klasa, z metodami dostępowymi dlanumber Itype.
  • aPerson_PhoneType Klasa ze statycznymi polami dla każdej wartości wPerson.PhoneType enum.

Możesz przeczytać więcej o szczegółach dokładnie tego, co jest generowane w instrukcji generowania kodu Dart.

pisanie wiadomości

teraz spróbujmy użyć klas bufora protokołu. Pierwszą rzeczą, którą chcesz, aby aplikacja do książki adresowej była w stanie zrobić, to napisać dane osobowe do pliku książki adresowej. Aby to zrobić, musisz utworzyć i wypełnić instancje klas buforów protokołu, a następnie zapisać je do strumienia wyjściowego.

oto program, który odczytujeAddressBook z pliku, dodaje jeden nowyPerson do niego na podstawie danych wejściowych użytkownika i zapisuje nowyAddressBook z powrotem do pliku ponownie. Części, które bezpośrednio wywołują lub odwołują się do kodu generowanego przez kompilator protokołu, są podświetlone.

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

czytanie wiadomości

oczywiście Książka Adresowa nie byłaby zbyt przydatna, gdybyś nie mógł uzyskać z niej żadnych informacji! Ten przykład odczytuje plik utworzony przez powyższy przykład i wypisuje wszystkie informacje w nim zawarte.

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

Rozszerzanie bufora protokołu

prędzej czy później po wydaniu kodu używającego bufora protokołu,niewątpliwie będziesz chciał „poprawić” definicję bufora protokołu. Jeśli chcesz, aby twoje nowe bufory były kompatybilne wstecz, a twoje stare bufory były kompatybilne wcześniej-a prawie na pewno tego chcesz-to musisz przestrzegać pewnych zasad. W nowej wersji bufora protocol:

  • nie wolno zmieniać numerów tagów istniejących pól.
  • możesz usunąć pola.
  • możesz dodawać nowe pola, ale musisz użyć nowych numerów znaczników (tzn. numerów znaczników, które nigdy nie były używane w tym buforze protokołu, nawet przez usunięte pola).

(istnieją pewne wyjątki od tych reguł, ale są one rzadko stosowane.)

jeśli zastosujesz się do tych zasad, stary kod z przyjemnością odczyta nowe wiadomości i zignoruje wszelkie nowe pola. W starym kodzie pojedyncze pola, które zostały wybrane, będą miały po prostu wartość domyślną, a usunięte powtarzające się pola będą puste. Nowy kod będzie również przejrzyście odczytywał stare wiadomości.

należy jednak pamiętać,że nowe pola nie będą obecne w starych wiadomościach, więc trzeba będzie zrobić coś rozsądnego z wartością domyślną. Specific Atype-specificdefault valuis used: w przypadku łańcuchów łańcuchowych wartością domyślną jest pusty łańcuch. Dla wartości logicznych wartością domyślną jest false. Dla typów liczbowych wartością domyślną jest zero.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.