Protocol Buffers, comment l'utiliser

Alternative à XML et aux bases de données, le format Protocol Buffers a été créé par Google pour sauvegarder les données structurées sous une double forme, textuelle facile à lire et binaire, compacte et simple à traiter pour les programmes.
Le format est extensible.

Pourquoi Protocol Buffers?

Ce format a été défini par Google comme alternative à XML afin de traiter plus rapidemment les fichiers, notamment pour ses propres besoins car ses millions de serveurs consomment des quantités de données.
Le moyen le plus simple de traiter les données, est qu'elle soient sous forme d'objet en mémoire et restent permanente dans un fichier, d'où le principe de sérialisation, qui consiste à sauver un objet dans son entier dans un fichier.
Cela peut se faire en XML, mais de même que JSON procure un moyen plus direct de le faire en JavaScript, PB procure un moyen direct de le faire pour d'autres langages de programmation comme C++ et Java. JSON se base sur les variables dynamiques de JavaScript, tandis que PB définit le type des données.
JSON convient mieux que XML pour le JavaScript, PB convient mieux que XML pour les autres langages car il n'a pas les défauts de XML, une grosse consommation d'espace et un traitement assez lourd pour convertir les données. Il conserve toutefois ses avantables: la flexibilité et la lecture facile.

Bien que PB ne fonctionne pour le moment qu'avec C++ et Java, des contributeurs travaillent à le porter sous d'autres langages et notamment PHP et Silverlight, il devrait donc appartenir au paysage du Web dans un futur proche.

Comment fonctionne PB?

Le compilateur fournit par Google permet, à partir de la description d'un document proto, de générer la classe pour les logiciels. Cette classe contient les méthodes qui automatiquement vont coder les données dans un fichier binaire ou charger un fichier binaire et le décoder.
Ultérieurement il sera possible d'étendre la définition tout en pouvant continuer à lire les fichiers déjà créés.

Ainsi on franchit les étapes suivantes:
- Vous définissez la structure du protocole.
- Le compilateur génère une classe, qui est une sous-classe de la classe Message.
- Vous écrivez un programme utilisant cette classe.
- Vous le compilez en incluant la classe générée ainsi que la librairie protobuf.
- Les données peuvent être sauvées dans un fichier proto par une fonction de sérialisation héritée de la classe Message.
- Vous pouvez aussi créer un service web et définir le canal d'échange avec le client.
- Les données de votre programme sont échangées entre le client et le serveur selon votre protocole.

Comment utiliser Protocol Buffers

Utiliser proto est très simple. Voici les étapes pour créer un premier programme avec Protocol Buffers.

1) Télécharger les archives

Vous aurez besoin de deux archives pour le compilateur et la bibliothèque à inclure.
Sur le site de Google Protocol buffer choisissez les archives correspondant à votre système.

Extraire le contenu, ce qui crée un répertoire Protobuf et fait apparaître le compilateur protoc.

2) Compiler la librairie protobuf

La procédure dépend du langage, du compilateur et du système et cela est expliqué dans le fichier README.txt à la racine du répertoire Protobuf extrait de l'archive.
Pour illustrer notre exemple, nous utiliserons Visual Studio Express sous Windows.

Lancer VSE et charger le fichier projet situé dans le répertoire vsprojects.
Lancer la compilation, elle sera probablement précédée d'une conversion car le fichier projet est voulu compatible avec les versions anciennes de VSE.
Le makefile va recréer des quantités de fichiers, mais tout ce qui nous intéresse c'est:

protobuf.lib
protobuf.dll

Ainsi éventuellement que les fichiers accompagnant protobuf.dll, avec une extension ajoutée.

3) Créer un environnement de développement

Pour des raisons pratiques, il convient de créer un nouveau répertoire pour notre projet. On peut l'appeller "proto" par exemple.
Dans ce répertoire, on recopie:

protoc.exe
protobuf.lib protobuf.dll et les fichiers d'accompagnement. google --- le sous-répertoire de Protobuf

Ce sont le compilateur proto (protoc.exe sous Windows), les librairies, et le répertoire des fichiers d'en-tête à inclure.

4) Définir un fichier proto

Dans ce fichier minimaliste, nous ajoutons un seul champ de donnée initialisé avec le texte "Hello World!".

Nous définissons un champ de texte que nous appellons "data":

message hello
{
required string data;
}

Nous ajoutons un numéro d'ordre:

message hello
{
required string data = 1;
}

Nous ajoutons une valeur par défaut, qui initialise le champ, et qui est entre crochets:

[default="Hello World!"]

Ce qui donne la définition complète suivante:

message hello
{
required string data = 1 [default="Hello World!"];
}

Le fichier est sauvé sous le nom hello.proto dans notre répertoire de travail.

5) Compiler le fichier proto

On a créé un fichier batch, nommé makehello.bat qui contient la commande suivante:

protoc --cpp_out=. hello.proto

le paramètre --cpp_out spéficie le répertoire de destination, le point indique le répertoire courant.
hello.proto est le fichier de définition précédemment créé.

La compilation va produire les fichiers suivants:

hello.pb.cc
hello.pb.h

Le compilateur protoc a généré la classe C++ correspondant à notre définition et le fichier d'en-tête correspondant.
Il nous reste à créer un programme en langage C++ pour utiliser cette classe.

6) Ecrire le programme hello

L'exemple d'utilisation de la classe consistera simplement à afficher le message contenu dans le fichier. Puis nous modifierons le contenu du champ en lui assignant une autre chaîne.

int main()
{
hello demo; // instance "demo" of the "hello" class defined in proto

const std::string& test = demo.data(); // data is the field where the text was stored
cout << test << "\n";

demo.set_data("How do you do?"); // changing the data
cout << demo.data() << "\n";
return 0;
}

L'instuction demo.data() permet d'accéder au contenu du champ data de notre définition proto. Cette interface est générée par le compilateur protoc.
Il génère aussi l'instruction demo.set_data() qui permet de modifier le contenu du champ.

Le code source complet est dans l'archive.

7) Compiler le programme hello

Le fichier batch sera complété de deux lignes. La première pour compiler le fichier généré par protoc ce qui produit hello.pb.obj, la seconde pour compiler notre propre script:

protoc --cpp_out=. hello.proto

cl hello.pb.cc /EHsc -I. -c

cl hello.cpp  /EHsc -I. hello.pb.obj libprotobuf.lib

Dans la seconde commande de compilation, on a inclu le fichier hello.pb.obj et l'interface du runtime protobuf.lib (qui permet d'accéder au contenu de protobuf.dll).

Lancer makedemo.bat, le programme hello.exe est créé. Il doit afficher:

Hello World!
How do you do?

8) Sauvegarder les données de la classe

Grâce aux méthodes héritées de la superclasse Message, le contenu modifié dans notre classe peut être conservé de façon permanente et retrouvé lors des utilisations futures du fichier proto ou par le même programme ou un autre.

Pour ce faire on peut utiliser la fonction C++ fstream en combinaison avec la méthode héritée SerializeToOstream.

Nous définissons un nouveau fichier proto simpliste:

message hello
{
  required int32 data = 1 [default=10];
}

Le corps de la fonction devient alors:

fstream input("hellowrite.proto", ios::in | ios::binary);

cout << demo.data() << "\n";
  
demo.set_data(demo.data() + 10);     // adding a value
  
fstream output("hellowrite.proto", ios::out | ios::trunc | ios::binary);
demo.SerializeToOstream(&output);  
  

A chaque fois que nous lançons le programme, le nombre affiché est augmenté de 10 ce qui nous prouve que le contenu de data est bien sauvegardé sur disque. En fait un nouveau fichier proto binaire est créé avec la nouvelle valeur stockée.

Le code source complet est dans l'archive.

9) Utilisation sur un serveur

Le compilateur crée aussi des interfaces entre le serveur et les clients, pour cela il faut définir un service:

service hello 
{    
    rpc data(HelloRequest) returns(HelloResponse);  
}  

Le processus de la création d'un service est décrit dans l'API Protocol Buffers.

data_Stub(RpcChannel* channel)

L'instruction crée un accès au channel auquel on peut envoyer un message.
Ceci est détaillé dans le tutoriel de Google.

Aller plus loin

Ceci n'est qu'une introduction à l'utilisation du langage Protocol Buffer. Pour une explication détaillée du format, allez sur le site de Google:

Consulter également le fichier d'en-tête généré par protoc, il fournit la liste des méthodes disponibles.

Les exemples de ce tutoriel sont réduits au minimum utile à la compréhension, ils ne sont pas utilisables tels quels en production. Dans vos applications de nombreux contrôles devront être ajoutés.

Télécharger les examples