1. TD numéro 1 : Communication via des sockets

1.1. Objectif:

Avant de considérer la programmation distribuée il faut à minima savoir comment faire communiquer des processus différents via des échanges de messages. Une des méthodes les plus générales et élémentaire pour ce faire consiste à simplement passer des messages sur le réseau. C’est le cas de millers de protocole, comme par exemple le protocole http utilisé pour distribuer des page web. Par ailleurs c’est souvent par dessus ces méthodes de communication que sont construites des couches plus hautes et plus complexes (appels de procédure distants, appel de méthodes rpc, rmi).

Il existe d’autres méthodes permettant de communiquer, en particulier dans le cas de processus s’exécutant sur une même machine, ceux ci partageant le système d’exploitation, la mémoire et le système, il leur est possible de communiquer de bien d’autres manières (thread, signaux unix, mémoire partagée, messages via le système de fichier).

Les protocoles réseau courants sont orientés paquet et non pas session ou connexion. Ainsi quand on écoute un flux d’entrée il n’existe à priori aucun moyen de savoir si l’autre intervenant dans la communication est encore actif, sa réponse peut être simplement tardive, mais il peut aussi être en panne (plantage), etc. La couche TCP/IP ne garanti que deux choses : les paquets arrivés sont valides, et il arrivent dans le bon ordre.

1.2. Serveur Mono, ne traitant qu’un client

1.2.1. Le B.A.BA , un serveur jouet

Écrire une classe Serveur_Jouet qui :

  1. crée un gestionnaire de socket sur le port 12000 (ServerSocket).
  2. Attend une connections entrante et associe à celle-ci une socket (Socket) quand elle arrive.
  3. Lit de façons bloquante et ligne à ligne les données recues et les affiche à l’écran avec un compteur de lignes (on utilisera les classes InputStreamReader BufferedReader).
  4. On veillera à traiter au minimum les erreurs potentielles d’entrée sortie.

Afin de tester le fonctionnement on utilisera telnet (telnet 127.0.0.1 12000) pour se connecter au Serveur Jouet.

  1. Que ce passe t’il si on lance plusieurs sessions telnet ?
  2. Que deviennent les sessions telnet si on arrête le serveur ?
  3. Que se passe t-il si on lance une seconde instance du Serveur Jouet ?

1.2.2. Un Serveur Amélioré

On veut modifier le Serveur afin qu’il puisse traiter sequentiellement

plusieurs connections. Afin que cela soit possible il faut pouvoir terminer une connections. La terminaison va s’opérer à l’initiative du client, celui-ci terminera la connection en envoyant une séquence de caractère spéciale que nous notons FINISH.

On rendra compte du comportement du serveur (démarrage, prise en charge, terminaison etc ...) via des messages sur la sortie standard.

À partir de la classe Serveur_Jouet, définir la classe Serveur_multi qui obéit aux spécifications suivantes :

  1. Un des créateurs (à priori c’est le seul) associé à classe prend comme argument le numéro de port sur lequel le serveur va écouter, cet argument est fourni par l’utilisateur (ex: java Serveur_Multi 1234). Il sera aussi souhaitable de vérifier que l’argument correspond bel et bien à un numéro de port correctement formaté.
  2. La méthode run() Examine si des connections sont actives sur Server_Socket, si oui elle en prend une en charge et lui applique la méthode Service_Client.
  3. La méthode Service_Client lit puis affiche (sur la sortie standard) les données envoyées par le client (comme dans le cas du Serveur_Jouet. Cependant elle recherche dans chaque ligne envoyée par le client la séquence de terminaison FINISH, si cette séquence apparait la méthode Service_Client se termine, la socket est alors close et l’on retourne écouter le Serveur_Socket.
  4. Le choix de la séquence de terminaison FINISH vous est laissé, attention toutefois de ne pas utiliser quelque chose de trop commun (pex “\(\backslash\)n”, “ab”). Le caractère EOT, ctrl-D dont le code ASCII est 4 est un choix acceptable.

Tests: (telnet encore)

  1. Que se passe t’il si plusieurs sessions telnet sont lancées concurremment ?
  2. En quoi envoyer la séquence FINISH diffère de “tuer” le processus telnet (kill, ctrl-c) ?
  3. Faire divers tests avec des telnet concurrents que l’on terminera.
  4. Quelle entité gère les connections bloquées en attente de service ?

1.3. Un Client Basique

1.3.1. Un client Put

Ce client jouet sera implémenté via une classe Mini_Client, qui s’utilisera comme suit : java Mini_Client hote port NOM. On modifiera très légerement Serveur_Multi afin qu’il detecte le nom du client et accuse réception de la connection.

  • Le client cherchera à se connecter au serveur, ensuite une fois la connexion établie :
  • il annoncera son NOM , pour cela il utilisera une annonce formatée selon des règles de votre choix (par exemple “Hello je suis: NOM”).
  • Le serveur détectera cette annonce en utilisant un analyseur d’expressions régulières (classes Pattern, Matcher du paquet java.util.regex. Pour tester l’expression régulière on pourra utiliser http://www.regexplanet.com/advanced/java/index.html et determinera ainsi le NOM du client.
  • Le serveur retournera alors un accusé de réception (pex “Bonjour NOM”).
  • Le client transmettra alors des données (on pourra au choix transmettre une chaîne aléatoire (i.e n importe quoi) ou structurée (pex. l’alphabet répété 100 fois) de caractère, ou le contenu d’un fichier).
  • le client cloturera alors la session.

Option : Bien que la correction des erreurs soit assurée par la couche réseau tcp/ip , pour s’assurer de la bonne transmission on pourra :

  • Demander que le serveur retourne comme accusé de réception le hash des données reçues.
  • Le client controlera alors que ce hash correspond bien à ses données, si oui il cloture la session.
  • Sinon le client renverra les données, jusqu’à ce que la transmission soit correcte.

1.3.2. Pour aller plus loin : Put et Get

Section optionnelle à faire à la maison pour les motivés. Étoffer le client et le serveur afin qu’un client puisse effectuer les trois opérations suivantes :

  1. Consulter la liste des données stockées par le serveur.
  2. Poser un ensemble de données sur le serveur , cet ensemble sera identifié par les couple (nom du client, nom de la donnée).
  3. Récupérer un ensemble de données.
Afin d’éviter des problèmes lié au passage de messages de contrôle et

de données sur le même canal on pourra décider que toute ligne envoyée commençant par ctrl-D est une ligne de contrôle et supposer que les données ne contiennent pas telles lignes ou échapper de telles lignes.

Le serveur et le client étant assez symétrique on pourra essayer de structurer le programme afin de leur faire partager du code ou des objets.