Université Paris Sud Licence d’informatique/IUP2 - Année 2003/2004 TD PAC - Java n◦ 10 (Correction) Applications en mode Client-Serveur Exercice 1 On désire réaliser une application de type Serveur de fichiers en mode client-serveur. 1. Le Serveur Pour cela on va créer une classe Serveur qui possèdera un attribut de type ServerSocket qui sera initialisé dans le constructeur pour attendre des connexions sur un numéro de port, éventuellement passé en ligne de commande (sinon on prendra comme port par défaut : 3456). Lors de la connection d’un client, le serveur devra créer un nouveau thread pour traiter les requètes en provenance de ce client. Bien sûr, pendant ce temps, le serveur devra continuer d’attendre de nouvelles connection éventuelles. Correction : /* * Fichier Serveur.java */ import java.io.*; import java.net.*; /** * Serveur de connection pour les clients. * Le choix du port de communication peut ^ etre passé en ligne de commande. */ public class Serveur { // Constantes private static final int PORT_PAR_DEFAUT = 3456; // Attributs private ServerSocket _sock; // Constructeur public Serveur (int port){ try { // Creation du socket d’écoute _sock = new ServerSocket (port); for (;;) { System.out.println ("Server: en attente "); // Detétection d’une connection Socket s = _sock.accept(); // Création d’un thread pour gérer la connection (new Connexion(s)).start(); } } catch (IOException e){ System.out.println("ServerSocket: " + e); System.exit (1); } } /** programme principal*/ public static void main (String [] args) { 1 int numero_de_port ; // Choix du numero de port if (args.length > 0) numero_de_port = Integer.parseInt(args[0]); else numero_de_port = PORT_PAR_DEFAUT; // Lancement du serveur Serveur s = new Serveur (numero_de_port); } } 2. La gestion des connections Nous considèrerons ici tout simplement qu’une requète est simplement une chaı̂ne de caractères qui correspond • soit à un nom d’un fichier a transférer (résidant sur la machine où tourne le serveur) • soit à la commande fin qui provoque l’arrêt du serveur. Le thread traitant les requètes sera implémenté par la classe Connexion. Son constructeur est appelé avec le Socket issu du accept qu’a fait le serveur. Sa méthode run doit lire la requète en provenance du client, la traiter, fermer la connexion, puis se terminer. Correction : /* * Fichier Connection.java */ import java.io.*; import java.net.*; /** * Thread qui gère une connection au serveur. * On peut la faire attendre le temps qu’on veut, * la planter éventuellement, le serveur reste à * l’écoute des autres demandes de service. **/ class Connexion extends Thread { // Attributs /** le socket retourné par <tt>accept</tt> */ private Socket _sock; // Constructeur public Connexion (Socket s) { _sock = s; } /** * Traitement d’une requète :<br> * 1. on lit la requète<br> * 2. si c’est <tt>‘fin’</tt> on termine le serveur<br> * 3. sinon on espère que c’est le nom d’un fichier à transmettre<br> * on le recopie octet par octet sur le socket.<p> * A la fin (exception de fin de fichier) on ferme la connexion et on * termine la thread. */ public void run (){ String requete = null; try { 2 for (;;) { BufferedReader in = new BufferedReader( new InputStreamReader(_sock.getInputStream ())); // lire la requète. requete = in.readLine(); if (requete.equals ("fin")) { System.out.println ("Arr^ et du serveur par un client"); System.exit (0); } // ouvrir le fichier correspondant. // On utilise un DataInputStream car on ne connait pas la nature du fichier DataInputStream din = new DataInputStream (new FileInputStream (requete)); System.out.println ("Ouverture du fichier : "+ requete); // recopier le fichier (on s’en sortira par l’exception de fin de fichier). DataOutputStream dout = new DataOutputStream (_sock.getOutputStream ()); for (;;) dout.writeByte (din.readByte ()); } } catch (FileNotFoundException e) { System.out.println ("Error: " + e.getMessage ()); } catch (EOFException e) { System.out.println ("Fichier : "+ requete + " Envoyé"); } catch (IOException e) { System.out.println ("run: " + e); } try { _sock.close (); } catch (IOException e) {} } } 3. Le Client Pour tester notre serveur de fichiers, on va écrire un autre programme qui va demander au serveur de lui envoyer un fichier. Ce programme prend en argument (dans la ligne de commande) • le nom de la machine correspondant au serveur • le numéro de port sur lequel se connecter • la requête (i.e. le nom du fichier à transférer ou bien ”fin” Il est implémenté avec la classe Client qui utilisera un objet de type Socket pour se connecter au serveur. Le fichier récupéré est enregistré localement sous le même nom que celui utilisé dans la requête. Correction : /* * Fichier Client.java */ import java.io.*; import java.net.*; public class Client{ // Constantes private static final int PORT_PAR_DEFAUT = 3456; // Attributs private Socket _sock; // Constructeur public Client (String machine, int port, String requete){ DataOutputStream dout=null; 3 try { // Connection au serveur _sock = new Socket (machine, port); // Envoi de la requète au serveur PrintWriter out = new PrintWriter(_sock.getOutputStream ()); out.println(requete); out.flush(); if (! requete.equals("fin")) { // attente de la réponse du serveur DataInputStream din = new DataInputStream(_sock.getInputStream ()); dout = new DataOutputStream(new FileOutputStream(requete)); // on sortira de la boucle par l’exception de fin de fichier for (;;) { dout.writeByte(din.readByte ()); } } } /** sortie ’normale’ de la boucle for */ catch (EOFException e) { try { dout.close(); _sock.close(); } catch (IOException ex) { System.err.println(ex); } } /** véritable erreur (pas de serveur, etc...) */ catch (IOException e) { System.err.println (e); try { _sock.close (); } catch (IOException ex) {} } } public static void main (String [] args) { int nb_args = args.length; String machine ; int numero_de_port = PORT_PAR_DEFAUT; String requete ; if ((nb_args < 2) || (nb_args > 3)) { System.err.println ("Usage: java client <machine> [<port>] <requete>"); } else { machine = args[0]; if (nb_args == 2) requete = args[1] ; else { numero_de_port = Integer.parseInt(args[1]); requete = args[2]; } Client c = new Client (machine, numero_de_port, requete); } } } 4