Fundamentos del búfer de protocolo: Dart

Este tutorial proporciona una introducción básica del programador de Dart para trabajar con búferes de protocolo, utilizando la versión proto3 del lenguaje de búferes de protocolo. Al recorrer la creación de una aplicación de ejemplo simple, le muestra cómo Definir formatos de mensaje en un archivo.proto.

  • Utilice el compilador de búfer de protocolo.
  • Utilice la API de búfer de protocolo Dart para escribir y leer mensajes.
  • Esta no es una guía completa para usar búferes de protocolo en Dart . Para obtener información de referencia más detallada, consulte la Guía de idiomas de Búfer de protocolo, el Recorrido de idiomas de Dart, la Referencia de API de Dart, la Guía de Código Generado de Dart y la Referencia de codificación.

    ¿Por qué usar búferes de protocolo?

    El ejemplo que vamos a usar es una aplicación de «libreta de direcciones» muy simple que puede leer y escribir los datos de contacto de las personas desde y hacia un archivo. Cada persona en la libreta de direcciones tiene un nombre, una identificación, una dirección de correo electrónico y un número de teléfono de contacto.

    ¿Cómo serializar y recuperar datos estructurados como este? Hay algunas maneras de resolver este problema:

    • Puede inventar una forma ad-hoc de codificar los elementos de datos en una sola cadena – como codificar 4 ints como «12:3:-23:67». Este es un enfoque simple y flexible, aunque requiere escribir código de codificación y análisis únicos, y el análisis impone un pequeño costo de tiempo de ejecución. Esto funciona mejor para codificar datos muy simples.
    • Serializar los datos en XML. Este enfoque puede ser muy atractivo, ya que XML es (una especie de) legible por humanos y hay bibliotecas de enlace para muchos idiomas. Esta puede ser una buena opción si desea compartir datos con otras aplicaciones/proyectos. Sin embargo, XML es notoriamente intensivo en espacio, y codificarlo/decodificarlo puede imponer una gran penalización en el rendimiento de las aplicaciones. Además, navegar por un árbol DOM XML es considerablemente más complicado de lo que normalmente sería navegar por campos simples en una clase.

    Los búferes de protocolo son la solución flexible, eficiente y automatizada para resolver exactamente este problema. Con los búferes de protocolo, escribe una .proto descripción de la estructura de datos que desea almacenar. A partir de eso, el compilador de búfer de protocolo crea una clase que implementa la codificación y el análisis automáticos de los datos del búfer de protocolo con un formato binario eficiente. La clase generada proporciona getters y setters para los campos que componen un búfer de protocolo y se encarga de los detalles de la lectura y escritura del búfer de protocolo como una unidad. Es importante destacar que el formato de búfer de protocolo admite la idea de extender el formato a lo largo del tiempo de tal manera que el código aún pueda leer datos codificados con el formato anterior.

    Dónde encontrar el código de ejemplo

    Nuestro ejemplo es un conjunto de aplicaciones de línea de comandos para administrar un archivo de datos de libreta de direcciones, codificado mediante búferes de protocolo.El comando dart add_person.dart añade una nueva entrada al archivo de datos. El comando dart list_people.dart analiza el archivo de datos e imprime los datos en la consola.

    Puedes encontrar el ejemplo completo en el directorio ejemplos del repositorio GitHub.

    Definir el formato de protocolo

    Para crear su aplicación de libreta de direcciones, deberá comenzar con un archivo.proto. Las definiciones de un archivo .proto son simples: se agrega un mensaje para cada estructura de datos que se desea jerializar y, a continuación, se especifica un nombre y un tipo para cada campo del mensaje. En nuestro ejemplo, el archivo .proto que define los mensajes esaddressbook.proto.

    El archivo.proto comienza con una declaración de paquete, que ayuda a evitar conflictos de nombres entre diferentes proyectos.

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

    A continuación, tiene sus definiciones de mensaje. Un mensaje es solo un agregado que contiene un conjunto de campos escritos. Muchos de los tipos de datos simples areavailable como los tipos de campo, incluyendo boolint32floatdouble y string. También puede agregar una estructura adicional a sus mensajes utilizando otros tipos de mensajes como tipos de campo.

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

    En el ejemplo anterior, el Person mensaje contienePhoneNumber mensajes, mientras que el AddressBook messagecontains Person mensajes. Incluso puede definir tipos de mensaje incrustados dentro de otros mensajes, como puede ver, el tipoPhoneNumber se define dentro de Person. También puede definir tipos enum si desea que uno de sus campos tenga uno de una lista predefinida de valores: aquí desea especificar que un número de teléfono puede ser uno de MOBILEHOME, oWORK.

    Los marcadores» = 1″,» = 2 «en cada elemento identifican la» etiqueta » única que el campo utiliza en la codificación binaria. Los números de etiqueta del 1 al 15 requieren un byte menos para codificar que los números más altos, de modo que, como optimización, puede decidir usar esas etiquetas para los elementos de uso común o repetidos, dejando las etiquetas 16 y superiores para los elementos opcionales de uso menos común. Cada elemento de un campo repetido requiere volver a codificar el número de etiqueta, por lo que los campos repetidos son candidatos particularmente buenos para esta optimización.

    Si no se establece un valor de campo, se utiliza un valor predeterminado: cero para tipos numéricos, la cadena vacía para cadenas, falso para bolos. Para mensajes incrustados, el valor predeterminado es siempre la «instancia predeterminada» o el «prototipo» del mensaje, que no tiene ninguno de sus campos establecidos. Llamar al accesor para obtener el valor de un campo que no se ha establecido explícitamente siempre devuelve el valor predeterminado de ese campo.

    Si un campo es repeated, el campo se puede repetir cualquier número de veces (incluido cero). El orden de los valores repetidos se conservará en el búfer de protocolo. Piense en los campos repetidos como matrices de tamaño dinámico.

    Encontrará una guía completa para escribir archivos .proto, incluidos todos los tipos de campos posibles, en la Guía del idioma del búfer de protocolos. Sin embargo, no busques instalaciones similares a la herencia de clases: los búferes de protocolo no hacen eso.

    Compilar sus búferes de protocolo

    Ahora que tiene un .proto, lo siguiente que debe hacer es generar las clases que necesitará leer y escribir AddressBook (y por lo tanto Person y PhoneNumber) mensajes. Para hacer esto, debe ejecutar el compilador de búfer de protocolo protoc en su .proto:

    1. Si no ha instalado el compilador, descargue el paquete y siga las instrucciones en el archivo LÉAME.
    2. Instale el complemento de Búfer de protocolo Dart como se describe en su LÉAME. El ejecutable bin/protoc-gen-dart debe estar en suPATH para el búfer de protocoloprotoc para encontrarlo.
    3. Ahora ejecute el compilador, especificando el directorio de origen (donde vive el código fuente de su aplicación, se usa el directorio actual si no proporciona un valor), el directorio de destino (donde desea que vaya el código generado; a menudo lo mismo que $SRC_DIR) y la ruta de acceso a su .proto. En este caso, invocaría:
      protoc -I=$SRC_DIR --dart_out=$DST_DIR $SRC_DIR/addressbook.proto

      Porque desea un código Dart, use la opción --dart_out: se proporcionan opciones similares para otros idiomas compatibles.

    Esto genera addressbook.pb.dart en el directorio de destino.

    La API de búfer de protocolo

    Que genera addressbook.pb.dart le proporciona los siguientes tipos útiles:

    • An AddressBook clase con unList<Person> get people getter.
    • Person clase con métodos de descriptor de acceso para nameidemail y phones.
    • Person_PhoneNumber clase, con métodos de descriptor de acceso para number y type.
    • A Person_PhoneType clase con campos estáticos para cada valor en la enumeración Person.PhoneType.

    Puede leer más sobre los detalles de exactamente lo que se genera en la guía de código generado de Dart.

    Escribir Un mensaje

    Ahora intentemos usar las clases de búfer de protocolo. Lo primero que desea que su aplicación de libreta de direcciones pueda hacer es escribir datos personales en su archivo de libreta de direcciones. Para hacer esto, debe crear y rellenar instancias de sus clases de búfer de protocolo y luego escribirlas en un flujo de salida.

    Aquí hay un programa que lee un AddressBook de un archivo, agrega un nuevo Personbasado en la entrada del usuario, y escribe el nuevo AddressBook de nuevo en el archivo. Se resaltan las partes que llaman directamente o hacen referencia al código generado por el compilador de protocolos.

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

    Leer un mensaje

    ¡Por supuesto, una libreta de direcciones no sería de mucha utilidad si no pudiera obtener ninguna información de ella! Este ejemplo lee el archivo creado por el ejemplo anterior e imprime toda la información que contiene.

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

    Extender un Búfer de protocolo

    Tarde o temprano después de liberar el código que usa su búfer de protocolo,sin duda querrá «mejorar» la definición del búfer de protocolo. Si desea que sus nuevos búferes sean compatibles con versiones anteriores y que sus búferes antiguos sean compatibles con versiones anteriores, y es casi seguro que lo desea, entonces hay algunas reglas que debe seguir. En la nueva versión del búfer de protocolo:

    • no debe cambiar los números de etiqueta de ningún campo existente.
    • puede eliminar campos.
    • puede agregar campos nuevos, pero debe usar números de etiqueta nuevos (es decir, números de etiqueta que nunca se usaron en este búfer de protocolo, ni siquiera por campos eliminados).

    (Hay algunas excepciones a estas reglas, pero rara vez se usan.)

    Si sigue estas reglas, el código antiguo leerá con gusto los mensajes nuevos y simplemente ignorará cualquier campo nuevo. Para el código antiguo, los campos singulares que se eliminaron simplemente tendrán su valor predeterminado, y los campos repetidos eliminados estarán vacíos. El nuevo código también leerá de forma transparente los mensajes antiguos.

    Sin embargo,tenga en cuenta que los campos nuevos no estarán presentes en los mensajes antiguos, por lo que deberá hacer algo razonable con el valor predeterminado. Se utiliza el valor predeterminado Atype-specific: para las cadenas, el valor predeterminado es la cadena vacía. Para los booleanos, el valor predeterminado es false. Para los tipos numéricos, el valor predeterminado es cero.

    Deja una respuesta

    Tu dirección de correo electrónico no será publicada.