Added more text in RAPPORT Changed debug level
8.9 KiB
大字报
Le Maoïsme du XXIème siècle
Étudiants préparant le projet de réseau, 2020
By 人民画报 - 《人民畫報》1967年, Public Domain, Link
Abstract
Dazi bao est l'implémentation d'un protocol de commérage, qui permet de publier un message, ce dernier est envoyé de proche en proche à chaque pair du réseau.
L'utilisation se fait depuis un terminal, en ligne de commande.
Structure et déroulement
Le programme est structuré autour du fichier node.c
, dans lequel se trouve la fonction main. Cette fonction va se charger d'appeler les fonctions d'initialisation, et de lancer le protocol. La construction des TLV, la production d'un hash et les fonctions de gestion et d'affichage d'erreurs sont dans leurs propre fichier, car ce sont des points qui peuvent être amené à changé lors de l'implémentation d'extensions.
Le noeud n'a pas de mémoire, et va donc être intialisé à partir de 0 à chaque lancement. Entre le lancement, et le moment où l'on connaît l'ensemble des messages publiés ( avec le site web comme référence, bien que ça ne soit pas forcément valide), le temps de convergance est en dessous d'une seconde.
Structure
Le programme se découpe en 2 parties ; l'initialisation, puis la récupération et publication des messages.
Initialisation
Lorsque l'on éxécute le programme, on donne une URL ainsi qu'un numéro de port. Cette URL va être décodée par getaddrinfo
, qui va nous donner >1 adresses IP, qui peuvent être en format IPv4 ou IPv6. Pour chaque adresse IP, nous crééons un pair, que l'on va marqué comme permanent. On y associe le numéro de port donné lors du lancement. Nous crééons aussi le socket que l'on utilisera ensuite. Nous le fixons sur le port 1212.
Par soucis de simplicité, et dans l'espoir que IPv4 disparaisse rapidement des internet, nous traitons tout les pairs comme étant en IPv6. Les adresses IPv4 sont IPv6 mapped. Une fois le programme lancé, on ne peux plus ajouter de pair permanent, ni les supprimer. On pourrait étendre le programme en permettant d'ajouter les pairs de façon dynamique, en les ajoutant ou supprimant. On pourrait aussi choisir de "bloquer" un pair, mais cela reste impossible en l'étant, voir la note 1.
Nous initialisations aussi la liste des messages, en créeant un permier message, vide. Il est vide, pour plusieurs raisons : d'une part, lorsque l'on lance le programme, on peux ne pas avoir envie de publier quoique ce soit, et de juste recevoir les messages. On peux aussi avoir envie d'effacer les messages publiés précédement. Lors de la publication de notre message, il remplacera les messages précédents connu des pairs par une phrase vide, ce qui effacera le message. À noter que, vu qu'il n'existe pas, dans le protocol de base, d'authentification, ça peux aussi être un moyen d'effacer tout les messages du réseau.¹
La liste de pair et la liste de messages sont des listes chainés simple. Elles sont suffisante, car permettent l'ajout et la suppression. Lors de l'envoie de messages, nous devons parcourir toute la liste des pairs, et lors de la réception ou l'inondation de message, nous devons aussi passer en revue tout les messages.
Une extension possible serait de stocker le hash de chaque message, afin d'avoir une hash map. On pourrait donc vérifier uniquement le hash, sans avoir besoin de le recalculer. Cela réduirait aussi fortement le temps de mise à jour et d'accès aux messages.
Déroulement
Le noeud va ensuite initiliser une structure pollfd
, un variable de délais, ainsi que deux tableau de char. Ces derniers font 1024 bytes, ce qui est recommandé dans le projet. C'est suffisant, car un paquet ne peux pas faire plus de 1024 bytes, et nous traitons les paquets les uns à la suite des autres. La structure pollfd
va servir lors de l'appel à la fonction poll
, qui se charge de surveiller deux descripteurs de fichiers : stdin
, et le socket définit lors de l'initialisation. Quand des données sont écrites dans ces fichiers, nous effectuons une lecture.
La lecture de stdin premet de récupérer à la fois des commandes pour la gestion du noeud, ainsi que le message à publier. La lecture du socket nous permet de récupérer les paquets entrant.
La variable de délais est utilisé pour mettre à jour la liste de pair, demander plus de pair le cas échéant, et demander l'état du réseau aux autres pairs. Nous l'initialisation à une valeur inférieure à 20 secondes, car lors du lancement, nous ne connaissons rien du réseau. Pas la peine d'attendre. Une fois la première éxécution passée, nous mettons un délais de vingt à trentes secondes. C'est suffisant, car la publication de nouveaux messages sur le réseau est assez lente. Nous pourrions faire évoluer ce délais en fonction du rythme de publication de nouveaux messages.
Ajouts de nouveaux messages
L'ajout de nouveaux messages est fait uniquement si on écrit un message non-vide. Dans le cas contraire, on affiche les messages connus.
On va ensuite appeler la fonction add_message(char * message, int message_len)
. Elle se charge de vérifier qu'on n'ajoute pas un message vide, puis va chercher le message publié par notre propre identifiant. On mets à jour le numéro de séquence, et l'on copie le nouveau message dans la liste. Afin d'éviter des erreures, on libère l'ancienne allocation, et on alloue une nouvelle mémoire de la taille du nouveau message.
Ce message sera envoyé aux pairs en faisant la demance lors de l'inondation.
Réception des paquets et traitement
Lors de la réception d'un message, on effectue plusieurs vérifications. La première consiste à vérifier l'entête du paquet. Une fois cette entête validée, on ajoute le pair à la liste des pairs connus.
On passe ensuite à la validation de chaque TLV. On commence par créer un paquet vide, qui va nous servir lors du renvoie éventuel de paquet vers les pairs. Ensuite, l'un à la suite des autres, nous validons les TLVs. Une fois qu'un TLV est valide, nous le traitons. Si nous avons besoin de renvoyer un paquet, nous allons soit le renvoyer directement, soit agrégé des TLVs dans un paquet, qui se remplira jusqu'a être pleins, suite à quoi il sera envoyé.
Gestion et génération des hashs.
?
Choix d'implémentation
TLV
Les TLVs sont représentés par un type union TLV
, qui contiens des pointeurs vers le struct
TLV en lui-même.
poll()
L'utilisation de poll()
nous permet de réagir à des évènements, et donc de ne pas avoir à attendre inutilement quand rien ne se passe. poll()
va attendre 10ms sur chaque descripteur de fichier, en bloquant le reste du programme. Si nous recevons un message alors que nous n'observons pas le socket, rien de grave ne se passe ; on attendra un autre message.
Envoie des paquets avec sendmsg
L'utilisation de sendmsg
ainsi que d'un buffer vectorisé n'a pas de réel avantage par rapport à un buffer classique. Mais, il pourrait permettre de stocker des messages à envoyer en l'attente d'une connexion. On pourrait ainsi envoyer plusieurs paquets d'un coup, au lieu de les envoyer les uns à la suite des autres.
Affichage et gestion des erreurs
Notre programme contient une variable DEBUG_LEVEL
définie dans debug.h
, qui permet de compiler avec plusieurs niveaux de verbosité. Lorsqu'elle est à 0, seul des messages d'information et d'erreur sont affichés, tel que l'ajout d'un message, la réception d'un message, ou l'affichage des messages connus.
Puis, entre 1 et 9, des message de débug avec plus ou moins de détails sont affichés, ce qui permet d'avoir une idée de la "vie" du pair en temps réel. À partir de 9, l'éxécution se fait étape par étape, en appuyant sur entrée à chaque étape.
Une attention particulière est porté sur la gestion des erreurs. En effet, notre pair va essayer au maximum de continuer à vivre malgré les bugs possible. Vu qu'il est possible de recevoir tout type de message, nous préférons afficher un message d'erreur ou de débug plutôt que d'arrêter l'éxécution. À noter que le fait de ne pas avoir de pair au début n'est pas une erreure fatale, car il se peut qu'un autre pair communique avec nous, sans que nous ne le connaissions au préalable.
Spécificités
?
Extensions
Nous avons implémenter l'aggrégation de TLV dans un paquet.
Nous vérifions la cohérence des Node State ; si on reçoit un node state, on vérifie que son hash est cohérent.
Si notre pair as plusieurs adresses, nous communiquons sur toutes les adresses qu'il possède.
Commentaire
Ce que l'on as pensé du projet, les éventuelles difficultés ?
Conclusion
?
[1] Pour cela, il suffit de publier un message vide, tout en se donnant comme numéro d'identification ceux que l'on connait, venant d'autres pairs.
Comment est organisé le code ? Diagram UML ? Dépendances ?