Université Paris 7 M2 II Protocole Internet TP2 Document à rendre: Vous devez déposer sur didel pour le 3 décembre: le code java des questions 7, 8 et 9(b) accompagné d’un cours rapport (environ 3 pages) présentant plus particulièrement la description du protocole de la question 9a et la manière dont vous avez testé votre application (question 10). Attention à bien respecter le format des messages. L’objectif de cet exercice est de réaliser une application distribuée pair-àpair en utilisant les sockets UDP en mode diffusion (i.e. permettant l’envoi d’un message à toutes les machines connectées au réseau en une seule opération d’envoi). On veut réaliser une application de discussion multipoint ("chat"). Votre programme devra envoyer sur les machines participantes de la salle toutes les lignes lues sur l’entrée standard et afficher sur la sortie standard tous les messages reçus. On utilise ici la classe MulticastSocket (java.net.MulticastSocket) qui permet des communications de groupe. Elle permet d’envoyer et de recevoir des paquets sur l’internet. Un groupe de multicast est identifié par une adresse IP de classe D et par un port UDP classique. Les adresses de classes D vont de 224.0.0.0 à 239.255.255.255. L’adresse 224.0.0.0 est réservée et ne doit pas être utilisée. 1. Création Ecrire une classe Com pour l’envoi et la réception de paquets de la discussion. Votre classe contiendra les champs : int port ; // Le port d’écoute du socket InetAddress multicastAddress ; // L’adresse IP utilisée pour la communication de groupe. MulticastSocket socket ; // Le socket identifié par son port Dans la phase de test vous choisirez le port 66i et l’adresse multicast 230.1.1.i où i est un numéro qui vous sera attribué au début du TP. Pour réaliser la discussion entre toutes les machines de la salle, vous utiliserez le port 6666 et l’adresse multicast 230.1.1.66. Dans la suite p désigne le port et am l’adresse multicast. 2. Ecrire un constructeur qui : 1 • initialise le port à p • crée le socket par socket = new MulticastSocket (port) • crée l’adresse multicast par multicastAddress = InetAddress.getByName ("am"), • indique que le socket va servir à recevoir les paquets à destination de cette adresse par socket.joinGroup (multicastAddress). La classe ByteBuffer (java.nio.ByteBuffer) est utilisée pour envoyer et recevoir des paquets. Un ByteBuffer b manipule de manière interne un tableau d’octets que l’on obtient par b.array(). On peut obtenir sa capacité par b.capacity(). On peut de plus lire et écrire à une position courante (obtenue par b.position()) par des méthodes de type get et put. Les lectures se font jusqu’à une position limite donnée par b.limit(). La méthode getString suivante sert à lire une String de longueur connue dans un ByteBuffer: static String getString (ByteBuffer b, int len) { byte[] t = new byte[len] ; b.get (t) ; return new String (t, 0, len) ; } La conversion de String vers byte[] se fait par la méthode getBytes(). 3. Ecrire une méthode send() qui prend en argument un ByteBuffer et envoie son contenu sur le socket par la méthode send de MulticastSocket. Seuls les octets entre le début du ByteBuffer et sa position courante doivent être envoyés. Il faudra pour cela construire un DatagramPacket à partir du ByteBuffer, de l’adresse am et du port p de destination. Vous pouvez utiliser le constructeur: DatagramPacket(byte[] buf, int length) pour affecter les données et la méthode setSocketAddress(SocketAddress address) pour affecter l’adresse destination am, et le port p. 4. Ecrire une méthode sendUnicast() qui prend en argument une SocketAddress et un ByteBuffer et envoie son contenu sur le socket par la méthode send de MulticastSocket. Seuls les octets entre le début du ByteBuffer et sa position courante doivent être envoyés. Il faudra pour cela construire un DatagramPacket à partir du ByteBuffer. Vous pouvez utiliser le constructeur: DatagramPacket(byte[] buf, int length) pour affecter les données et la méthode setSocketAddress(SocketAddress address) pour affecter l’adresse destination. 5. Ecrire une méthode receive() qui prend en argument un ByteBuffer, remplit son contenu par celui du paquet reçu et renvoie l’adresse et le port de l’envoyeur sous forme de SocketAddress. Utiliser pour cela la méthode receive(DatagramPacket x) de MulticastSocket pour recevoir un paquet. 2 Vous pouvez utiliser le constructeur DatagramPacket(byte[] buf, int length) pour partager le même tableau de bytes que le ByteBuffer. Mettre ensuite la position du ByteBuffer à 0 et sa limite à la longueur du DatagramPacket (qui est obtenue par getLength()). Renvoyer l’adresse et le port de l’envoyeur du paquet obtenus par getSocketAddress(). 6. Tester vos méthodes avec le code ci-dessous qui envoie 2 messages constitués des chaines lues dans les arguments du main et un numéro. import import import import java.net.InetAddress; java.net.SocketAddress; java.net.UnknownHostException; java.nio.ByteBuffer; public class Main{ static String getString (ByteBuffer b, int len) { byte[] t = new byte[len] ; b.get (t) ; return new String (t, 0, len) ; } public static void main (String[] args) { Com s = new Com(6666); ByteBuffer b = ByteBuffer.allocate(1400) ; InetAddress moi; int i=1; String msg; try { moi = InetAddress.getLocalHost(); } catch (UnknownHostException ex) { moi=null; } msg = "envoi de "+ moi.getHostAddress() ; for (String a : args) msg += " " + a ; msg= msg + " " +i; b.put (msg.getBytes()) ; s.send (b); SocketAddress from =s.receive (b); //on recoit son messge (ou un autre) System.out.println ("reception de " + from + " : " + getString (b, b.limit())) ; b.clear(); msg= msg + " message renvoye "; b.put (msg.getBytes()) ; s.sendUnicast (from,b); 3 !! System.out.println ("reception de unicast " + s.receive (b)+ " : " + getString (b, b.limit())) ; b.clear(); } } Pour pouvoir discuter clairement les uns avec les autres de manière plus robuste, on utilise un format particulier de message : ID est un champ indiquant la nature du message (codé sur 2 octets). On définit dans cette première version public static short DATA = 1 public static short BEGIN= 2 public static short LAST = 3 surnom identifie l’envoyeur, c’est une suite de 6 lettres (codée sur 6 octets). seq est un numéro de séquence (sur 2 octets) incrémenté par l’envoyeur chaque fois qu’il envoie un paquet DATA taille est la longueur du message qui suit sur 2 octets. message c’est la suite des caractères du message. 7. VERSION V0 Le protocole de communication indique que tous les messages de type DATA correctement formés suivant la description ci-dessus seront affichés dés qu’ils sont reçus (on ne se préoccupe pas ici de vérifier le champ seq) et tous les autres seront ignorés. Ecrire l’application de discussion réalisant le protocole simple de "chat" avec un thread pour la réception des messages, le thread principal étant dédié à l’envoi. 8. VERSION V1 Un nouvel utilisateur arrivant dans la discussion veut pouvoir recevoir le dernier message envoyé par tous les membres de la discussion. On utilisera un message de type BEGIN et de type LAST. BEGIN correspond à un nouvel arrivant et à une demande de réémission. LAST correspond à la réémission du dernier message envoyé (le champ seq sera alors le numéro de ce message pour l’émetteur). Ecrire l’application de discussion réalisant ce protocole enrichi. 4 9. VERSION V2 On veut maintenant afficher les messages reçus dans l’ordre dans lequel ils ont été envoyé. I.e. si on a affiché le x ème message d’un utilisateur on n’affichera ensuite qu’un message de numéro strictement supérieur à x. Dans une application réelle de discussion sur Internet, des paquets peuvent être perdus ou déséquencés, notamment en cas de congestion dans le réseau. (a) Définissez un protocole (en ajoutant de nouveaux types de messages) pour signaler la perte du message à son émetteur et demander la réémission des paquets perdus. Lors la définition de ce protocole vous aurez des choix à faire: quand, à qui demander la réémission d’un paquet, les champs du paquet demandant la réémission et du paquet de réponse.... Justifiez les choix que vous avez faits. (b) Réalisez une application suivant le protocole que vous avez défini. 10. On veut pouvoir tester une application réalisant le protocole de "chat" que vous avez défini. Ecrire un programme qui permet de tester l’application (par exemple en introduisant volontairement des pertes des messages et des déséquencements dans les messages.) 5