Bases du tampon de protocole: Dart

Ce tutoriel fournit une introduction de base au programmeur de fléchettes pour travailler avec des tampons de protocole, en utilisant la version proto3 du langage des tampons de protocole. En parcourant la création d’un exemple d’application simple, il vous montre comment

  • Définir les formats de message dans un fichier .proto.
  • Utilisez le compilateur de tampon de protocole.
  • Utilisez l’API Dart protocol buffer pour écrire et lire des messages.

Ce n’est pas un guide complet sur l’utilisation des tampons de protocole dans Dart. Pour des informations de référence plus détaillées, consultez le Guide du Langage du Tampon de protocole, la Visite de la Langue des Fléchettes, la Référence de l’API Dart, le Guide du Code Généré par les Fléchettes et la Référence d’encodage.

Pourquoi utiliser des tampons de protocole ?

L’exemple que nous allons utiliser est une application « carnet d’adresses » très simple qui peut lire et écrire les coordonnées des personnes vers et à partir d’un fichier. Chaque personne dans le carnet d’adresses a un nom, un identifiant, une adresse e-mail et un numéro de téléphone de contact.

Comment sérialiser et récupérer des données structurées comme celle-ci? Il existe plusieurs façons de résoudre ce problème:

  • Vous pouvez inventer un moyen ad hoc d’encoder les éléments de données en une seule chaîne – comme l’encodage de 4 ints comme « 12:3:-23:67 ». Il s’agit d’une approche simple et flexible, bien qu’elle nécessite l’écriture d’un codage et d’un code d’analyse uniques, et l’analyse impose un faible coût d’exécution. Cela fonctionne mieux pour encoder des données très simples.
  • Sérialiser les données en XML. Cette approche peut être très attrayante car XML est (en quelque sorte) lisible par l’homme et il existe des bibliothèques de liaison pour de nombreux langages. Cela peut être un bon choix si vous souhaitez partager des données avec d’autres applications / projets. Cependant, XML est notoirement gourmand en espace, et son codage / décodage peut imposer une énorme pénalité de performance aux applications. De plus, la navigation dans une arborescence DOM XML est beaucoup plus compliquée que la navigation dans des champs simples dans une classe ne le serait normalement.

Les tampons de protocole sont la solution flexible, efficace et automatisée pour résoudre exactement ce problème. Avec les tampons de protocole, vous écrivez une .proto description de la structure de données que vous souhaitez stocker. À partir de cela, le compilateur de tampon de protocole crée une classe qui implémente l’encodage et l’analyse automatiques des données de tampon de protocole avec un format binaire efficace. La classe générée fournit des getters et des setters pour les champs qui composent un tampon de protocole et s’occupe des détails de la lecture et de l’écriture du tampon de protocole en tant qu’unité. Fait important, le format de tampon de protocole soutient l’idée d’étendre le format au fil du temps de manière à ce que le code puisse toujours lire les données codées avec l’ancien format.

Où trouver l’exemple de code

Notre exemple est un ensemble de lignes de commandesapplications pour la gestion d’un fichier de données de carnet d’adresses, codé à l’aide de tampons de protocole.La commande dart add_person.dart ajoute une nouvelle entrée au fichier de données. La commande dart list_people.dart analyse le fichier de données et imprime les données sur la console.

Vous pouvez trouver l’exemple complet dans la direction des exemples du référentiel GitHub.

Définition du format de votre protocole

Pour créer votre application de carnet d’adresses, vous devez commencer par un fichier .proto. Les définitions dans un fichier .proto sont simples: vous ajoutez un message pour chaque structure de données que vous souhaitez sérialiser, puis spécifiez un nom et un type pour chaque champ du message. Dans notre exemple, le fichier .proto qui définit les messages est addressbook.proto.

Le fichier .proto commence par une déclaration de package, ce qui aide à éviter les conflits de noms entre différents projets.

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

Ensuite, vous avez vos définitions de message. Un message est juste un agrégatcontenant un ensemble de champs saisis. De nombreux types de données simples standard sont disponibles en tant que types de champs, y compris boolint32floatdouble, et string. Vous pouvez également ajouter une structure supplémentaire à vos messages en utilisant d’autres types de messages comme types de champs.

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

Dans l’exemple ci-dessus, le message Person contient des messages PhoneNumber, tandis que le message AddressBook contient Person messages. Vous pouvez même définir des types de messages à l’intérieur d’autres messages – comme vous pouvez le voir, le type PhoneNumber est défini à l’intérieur de Person. Vous pouvez également définir des types enum si vous souhaitez que l’un de vos champs en ait un d’une liste prédéfinie de valeurs – ici, vous souhaitez spécifier qu’un numéro de téléphone peut être l’un des MOBILEHOME, ou WORK.

Les marqueurs « =1 », « =2 » sur chaque élément identifient la « balise » unique que le champ utilise dans le codage binaire. Les numéros de balise 1 à 15 nécessitent un octet de moins pour coder que les nombres plus élevés, de sorte qu’à titre d’optimisation, vous pouvez décider d’utiliser ces balises pour les éléments couramment utilisés ou répétés, en laissant les balises 16 et supérieures pour les éléments optionnels moins couramment utilisés. Chaque élément d’un champ répété nécessite de ré-encoder le numéro d’étiquette, de sorte que les champs répétés sont particulièrement bons candidats pour cette optimisation.

Si une valeur de champ n’est pas définie, une valeur par défaut est utilisée : zerof pour les types numériques, la chaîne vide pour les chaînes, false pour les bools. Pour les messages incorporés, la valeur par défaut est toujours « instance par défaut  » ou « prototype  » du message, qui n’a aucun champ défini. Appeler l’accesseur pour obtenir la valeur d’un champ qui n’a pas été explicitement défini renvoie toujours la valeur par défaut de ce champ.

Si un champ est repeated, le champ peut être répété un nombre quelconque de fois (y compris zéro). L’ordre des valeurs répétées sera conservé dans le tampon de protocole. Considérez les champs répétés comme des tableaux de taille dynamique.

Vous trouverez un guide complet pour écrire des fichiers .proto – y compris tous les types de champs possibles – dans le Guide du langage de tampon de protocole. Ne cherchez pas d’installations similaires à l’héritage de classe, cependant – les tampons de protocole ne le font pas.

Compiler vos tampons de protocole

Maintenant que vous avez un .proto, la prochaine chose à faire est de générer les classes dont vous aurez besoin pour lire et écrire AddressBook (et donc Person et PhoneNumber) messages. Pour ce faire, vous devez exécuter le compilateur de tampon de protocole protoc sur votre .proto:

  1. Si vous n’avez pas installé le compilateur, téléchargez le package et suivez les instructions dans le fichier de lecture.
  2. Installez le plugin Dart Protocol Buffer comme décrit dans son fichier README. L’exécutable bin/protoc-gen-dart doit être dans votre PATH pour que le tampon de protocole protoc le trouve.
  3. Exécutez maintenant le compilateur, en spécifiant le répertoire source (où vit le code source de votre application – le répertoire actuel est utilisé si vous ne fournissez pas de valeur), le répertoire de destination (où vous souhaitez que le code généré aille; souvent le même que $SRC_DIR), et le chemin d’accès à votre .proto. Dans ce cas, vous appelleriez :
    protoc -I=$SRC_DIR --dart_out=$DST_DIR $SRC_DIR/addressbook.proto

    Parce que vous voulez du code Dart, vous utilisez l’option --dart_out – des options similaires sont fournies pour les autres langues prises en charge.

Cela génère addressbook.pb.dart dans votre répertoire de destination spécifié.

L’API de tampon de protocole

Générant addressbook.pb.dart vous donne les types utiles suivants :

  • Une AddressBook classe avec un List<Person> get peoplegetter.
  • Une Personclasse avec des méthodes d’accesseur pour nameidemail et phones.
  • Une classe Person_PhoneNumber, avec des méthodes d’accesseur pour number et type.
  • Une classe Person_PhoneType avec des champs statiques pour chaque valeur de l’énumération Person.PhoneType.

Vous pouvez en savoir plus sur les détails de ce qui est généré dans le guide du code généré par les fléchettes.

Ecrire un Message

Essayons maintenant d’utiliser vos classes de tampon de protocole. La première chose que vous voulez que votre application de carnet d’adresses puisse faire est d’écrire des informations personnelles dans votre fichier de carnet d’adresses. Pour ce faire, vous devez créer et remplir des instances de vos classes de tampon de protocole, puis les écrire dans un flux de sortie.

Voici un programme qui lit un AddressBook à partir d’un fichier, y ajoute un nouveau Person en fonction de l’entrée de l’utilisateur, et écrit à nouveau le nouveau AddressBook dans le fichier. Les parties qui appellent ou référencent directement le code généré par le compilateur de protocole sont mises en surbrillance.

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

Lire un Message

Bien sûr, un carnet d’adresses ne serait pas très utile si vous ne pouviez en extraire aucune information! Cet exemple lit le fichier créé par l’exemple ci-dessus et y imprime toutes les informations.

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

Extension d’un tampon de protocole

Tôt ou tard après avoir libéré le code qui utilise votre tampon de protocole, vous voudrez sans aucun doute « améliorer » la définition du tampon de protocole. Si vous souhaitez que vos nouveaux tampons soient rétrocompatibles et que vos anciens tampons soient compatibles avant – et vous le souhaitez presque certainement – il y a quelques règles à suivre. Dans la nouvelle version du tampon de protocole:

  • vous ne devez pas modifier les numéros de balise des champs existants.
  • vous pouvez supprimer des champs.
  • vous pouvez ajouter de nouveaux champs mais vous devez utiliser de nouveaux numéros de balises (c’est-à-dire des numéros de balises qui n’ont jamais été utilisés dans ce tampon de protocole, même pas par des champs supprimés).

(Il existe quelques exceptions à ces règles, mais elles sont rarement utilisées.)

Si vous suivez ces règles, l’ancien code lira avec plaisir les nouveaux messages et ignorera simplement les nouveaux champs. Pour l’ancien code, les champs singuliers supprimés auront simplement leur valeur par défaut et les champs répétés supprimés seront vides. Le nouveau code lira également de manière transparente les anciens messages.

Cependant, gardez à l’esprit que les nouveaux champs ne seront pas présents dans les anciens messages, vous devrez donc faire quelque chose de raisonnable avec la valeur par défaut. Une valeur par défaut spécifique au type est utilisée : pour les chaînes, la valeur par défaut est la chaîne vide. Pour les booléens, la valeur default est false. Pour les types numériques, la valeur par défaut est zéro.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.