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 bool
int32
float
double
I string
div>. 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 MOBILE
HOME
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
:
- jeśli nie zainstalowałeś kompilatora, pobierz pakiet i postępuj zgodnie z instrukcjami w README.
- Zainstaluj wtyczkę bufora protokołu Dart zgodnie z opisem w README. Plik wykonywalny
bin/protoc-gen-dart
musi znajdować się w TwoimPATH
dla bufora protokołuprotoc
, aby go znaleźć. - 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:
- An
AddressBook
klasa zList<Person> get people
getter. - a
Person
klasa z metodami akcesorowymi dlaname
id
email
Iphones
. - a
Person_PhoneNumber
klasa, z metodami dostępowymi dlanumber
Itype
. - a
Person_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.