L'objectif de ce projet est de réaliser un programme en Java pour échanger des images animées dans un réseau pair à pair (peer-to-peer) de type Gnutella. Il revêt deux principaux aspects : d'une part, la gestion du réseau de "peer" à "peer", et d'autre part, le transfert et la visualisation d'images animées.
OpCode
qui
devront être reconus, ici dans le
sujet, celui de la trame IMAGE est bien
0XFFFD
et non 0xDFFF
comme c'est
écrit dans la version papier qui vous a été distribuée (merci
à Fabien Fassler de l'avoir signalé).
Il existe de nombreuses variantes et implantations de réseaux peer-to-peer. Dans ce projet, nous nous intéressons à un réseau de type Gnutella, qui est un protocole pour des recherches distribuées. Dans ce type de réseau, chaque machine peut jouer simultanément le rôle de serveur et de client : aucune ne possède particulièrement, de manière prédéfinie, un rôle plutôt que l'autre. Aussi, on appelle ces machines des servents (serveurs-clients). Chaque machine dispose d'un ensemble de ressources qu'elle accepte de partager avec les autres machines du réseau peer-to-peer (dans notre cas, il s'agira d'images animées). Chaque servent dispose d'une interface utilisateur permettant d'effectuer une requête à propos d'une ressource, de rendre compte des réponses à cette requête dans le réseau et, le cas échéant, de télécharger cette ressource. Dans le cas particulier qui nous intéresse, il s'agira de visualiser, en temps réel, les images animées téléchargées.
Toute la partie concernant la gestion du réseau peer-to-peer Gnutella devra être compatible avec la spécification du protocole Gnutella, v0.4. Voici une brève description du mode de fonctionnement d'un tel réseau:
GNUTELLA CONNECT/0.4\n\nce à quoi le servent A répond, pour accepter la nouvelle machine M, la ligne :
GNUTELLA OK\n\nDans ces deux lignes, "\n" représente le caractère retour-chariot (line feed), de code ASCII
0x0A
.
Ces requêtes Ping vont être transmises dans le réseau Gnutella, de proche en proche en suivant les connexions TCP qui relient les servents, pour annoncer la présence de la machine M. Les servents peuvent alors répondre à ces Ping par des Pong, qui sont retransmis vers M afin de lui permettre de connaître plus de "voisins" dans le réseau. Cela peut permettre à M de créer de nouvelles connexions TCP avec d'autres servents du réseau. Un mécanisme de TTL (time to live) permet de limiter la propagation de ces requêtes. Dans l'exemple ci-dessous, M choisit de se connecter directement sur C, en plus de sa connexion avec A.
L'échange de Ping et de Pong peut avoir lieu à tout moment (en général régulièrement) au cours de la vie du réseau. En effet, c'est cet échange qui permet de garder une bonne "connectivité", en dépit des éventuelles déconnexions de servents.
La principale caractéristique de ces réseaux est qu'ils doivent être tolérants aux fautes. En particulier, il doit être possible qu'une machine se déconnecte à tout moment, sans remettre en cause le fonctionnement global du réseau.
Pour toute la partie concernant la gestion des servents du réseau Gnutella, vous vous attacherez à respecter la spécification du protocole Gnutella, v0.4. Néanmoins, la partie de cette spécification qui décrit les requêtes Push (pour les machines situées derrière un firewall) ne sera pas implantée.
Concernant le téléchargement et l'affichage en temps réel, l'objectif est de pouvoir visualiser une ressource sans la stocker sur le disque. Il faut donc disposer d'un mécanisme assurant, au dessus d'UDP, que le client et le serveur de cette ressource collaborent à la fluidité de l'affichage.
Cette partie décrit le processus de transfert d'images, qui
intervient lorsqu'un servent (client) a reçu un
QueryHit identifiant le servent (serveur) qui met
à disposition la ressource (identifiée par le serveur par un
index FileIndex
) sur une adresse IP et un port UDP
(au lieu de TCP dans le protocole original) connu du client.
Nous nous limitons, dans la description ci-dessous, au cas où l'image animée est constituée d'une séquence d'images. De nombreux autres cas peuvent être considérés (formats de films) et peuvent donner lieu à des extensions des formats décrits ci-après.
Pour réaliser le transfert, le client et le serveur vont échanger une suite de datagrammes UDP permettant de télécharger la séquence des images. La taille maximale de ces datagrammes est fixée à 1024 octets et leur format est le suivant (les positions et décalage des champs sont exprimés en octets):
0 3 4 5 6 +-----------+------+------------------- - - - - - - -----+ |File Index |OpCode| Données dépendant de OpCode | +-----------+------+------------------- - - - - - - -----+
Regardons maintenant les formats des trames correspondant aux
différentes valeurs prédéfinies de OpCode
.
Attention: comme pour la spécification de Gnutella, tous les champs dans les trames ci-dessous doivent être écrits et lus en respectant l'ordre petit-boutiste des octets (little-endian), à moins qu'il soit précisé autre chose.
HELLO
Le premier envoi du client vers le
serveur a le format suivant, correspondant à un
OpCode
de 0xFFFF
, dit
HELLO
(le reste de la trame est vide).
0 3 4 5 +-----------+------+ |File Index | FFFF | +-----------+------+
NEGO
Le serveur envoie au client, en
réponse à une trame HELLO
, une trame de
négociation, dite NEGO
, correspondant à un
OpCode
situé entre 0x0001
et
0x09FF
. Cette trame NEGO
permet au
serveur d'indiquer au client le mode de fragmentation des
images, spécifié par la valeur de l'OpCode
.OpCode
est 0x0001
, correspond à une fragmentation
linéaire simple. La trame, décrite ci-dessous, contient alors
le délai (en millisecondes) séparant l'envoi de deux images
consécutives (la cadence), ainsi que la largeur et la hauteur
en pixels des images envoyées par le serveur. En fonction de
l'OpCode
, on peut ajouter d'autres champs à ce type
de trame.
0 3 4 5 6 9 10 13 14 17 +-----------+------+----------+-----------+-----------+ |File Index | 0100 | delay | width | heigth | +-----------+------+----------+-----------+-----------+Si le client accepte les spécifications décrites dans la trame
NEGO
reçue du serveur, il peut débuter le
transfert par une trame dite DOWNLOAD
(ci-dessous). Sinon, le client peut exprimer ses exigences en
renvoyant une trame NEGO
au serveur. Outre le
délai, le client peut, par exemple, spécifier la largeur et la
hauteur d'image qu'il souhaite recevoir. S'il accepte, le
serveur renverra alors une trame NEGO
identique
de confirmation, et prendra à sa charge la "mise à la taille"
des images dont il dispose avant de les envoyer au client
(tant que le serveur ne fournit pas de trame NEGO
acceptable par le client, le transfert ne peut pas
débuter). Cette utilisation des champs width
et
height
peut être vue comme un mécanisme minimal
d'adaptation au débit du réseau traversé par les paquets UDP
pour la fluidité de l'affichage).
DOWNLOAD
Pour débuter le transfert, le
client envoie donc une trame DOWNLOAD
,
correspondant à un OpCode
de 0xFFFE
,
qui demande au serveur de débuter l'envoi des données. Cette
trame spécifie le numéro de la première image de la séquence
que le client veut recevoir.
0 3 4 5 6 9 +-----------+-----+-----------+ |File Index |FEFF | image N° | +-----------+-----+-----------+L'envoi d'une trame
DOWNLOAD
peut être envisagé à
n'importe quel moment, par exemple pour redemander l'envoi des
données depuis un numéro d'image dans la séquence.
IMAGE
Enfin, l'envoi des (fragments d')
images du serveur vers le client se fait par des trames dites
IMAGE
, correspondant à un OpCode
de
0xFFFD
. Celles-ci spécifient le numéro de l'image
dans la séquence à laquelle appartient le fragment, la taille
totale de l'image originale et le décalage du fragment par
rapport au début de l'image (exprimé en octets).
0 3 4 5 6 9 10 13 14 17 18 +-----------+-----+-----------+-----------+-----------+---- - - -----+ |File Index |FDFF | image N° |image size | offset | fragment | +-----------+-----+-----------+-----------+-----------+---- - - -----+
OpCode
, peut être réalisé en
utilisant des OpCode
à partir de
0xA000
, pour implanter des fonctionnalités
supplémentaires permettant, par exemple, d'ajuster la qualité
de l'image pendant le transfert, ou d'interrompre le
téléchargement. Dans tous les cas, les formats ci-dessus
correspondant aux OpCode
0xFFFF
(HELLO
), 0x0001
(NEGO
de base), 0xFFFE
(DOWNLOAD
) et
0xFFFD
(IMAGE
) devront être reconnus
et utilisables.
Le client, au fur et à mesure de la réception des paquets, doit reconstruire les images. À chaque fois qu'il devient nécessaire d'afficher une image, le client doit se débrouiller avec les paquets qu'il a reçu, même s'il lui en manque, afin de ne pas interrompre la fluidité de l'animation. En fonction des formats d'images utilisés, plusieurs méthodes de "remplissage des trous" peuvent être imaginées (mettre du noir, utiliser l'image précédente, utiliser des formats d'image progressifs, etc.)
Quand le débit devient trop faible comparé à la taille des
images, le client devra demander au serveur de baisser la
qualité des images envoyées afin que les données à transférer
pour chaque image soient plus petites : le mécanisme
minimal à mettre en oeuvre est celui décrit plus haut dans la
description de la trame NEGO
d'OpCode
0x0001
, mais d'autres OpCode
avec
d'autres mécanismes pourront être implantés.
Vous devez écrire en Java un programme qui prend en charge le fonctionnement d'un servent Gnutella. Installé et configuré sur une machine, ce programme pourra fonctionner de manière autonome pour ce qui concerne la gestion des connexions du réseau Gnutella, pour les réponses aux requêtes Query des autres servents, ainsi que pour la mise à disposition des ressources par le serveur UDP. Pour la partie recherche, téléchargement et affichage d'images animées, le programme offrira une interface avec l'utilisateur. Par ailleurs, cette interface pourra avantageusement permettre d'observer les caractéristiques de ce servent (nombre et identité de ses "voisins", ressources offertes sur le réseau, activité sur le réseau en tant que "routeur" et en tant que "serveur", etc.).
Il faut implanter au moins le mode de découpage linéaire des images qui correspondra à l'OpCode
0x0001
. Lorsque le serveur utilise ce mode de
découpage, le fichier image est découpé linéairement en
fragments (en tête inclus), et envoyé au client. Il suffit au
client de remettre les fragments les uns après les autres pour
obtenir un fichier image que l'on peut afficher avec les
routines de javax.imageio
et notamment la classe
javax.imageio.ImageIO
. Ce mode de découpage
étant peu efficace face aux pertes de paquets (notamment le
premier qui contient l'en-tête), on pourra en implanter
d'autres.
Vous devrez prendre garde à développer avec soin la partie concurrente de l'application (nombre de threads, nombre de connexions optimum, maximum, nombre de clients simultanés en téléchargement, etc.).
Vous rendrez votre travail sous la forme d'une archive contenant les sources commentés, la Javadoc de ces sources, un jar exécutable de l'application et un fichier README permettant de l'installer et de le configurer.
http://igm.univ-mlv.fr/~duris/RESEAU/gnutella_protocol_0.4.pdf
http://igm.univ-mlv.fr/~duris/RESEAU/projet2003.html
http://java.sun.com/j2se/1.4.1/docs/guide/imageio/spec/title.fm.html
Nicolas Bedon, Julien Cervelle, Etienne Duris, Rémi Forax © Université de Marne-La-Vallée - Mars 2003