Communication en mode connecté modèle client-serveur                  

Communication en mode connecté modèle client-serveur

     

L'exemple est ici de type client-serveur. Un serveur attend des clients pour leur rendre le service suivant : le client envoie un texte issu d'un fichier de type texte, ligne par ligne, et le serveur compte le nombre total de mots du texte.

Le client indique sur la ligne de commande le nom d'un fichier dont il veut compter les mots et le nom de la machine serveur. Le port du serveur sur lequel se fait l'attente de demande de connexion a été fixé dans le programme et vaut 10302. Lorsque le texte a été totalement envoyé, le client envoie la chaîne de caractères "*+*+*+*+" pour indiquer au serveur la fin de l'envoi.

Voici le programme client.


import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.net.UnknownHostException;

class Client {	
	public static void main(String[] arg) {
		int portEcouteServeur = 10302;
		BufferedReader lecteurFichier;
		BufferedReader entree;
		PrintStream sortie;
		String ligne;
		Socket socket;
		try {
			socket = new Socket(arg[1], portEcouteServeur);
			lecteurFichier = new BufferedReader(new FileReader(arg[0]));
			entree = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			sortie = new PrintStream(socket.getOutputStream());
			while ((ligne = lecteurFichier.readLine()) != null) sortie.println(ligne);
			sortie.println("*+*+*+*+");
			System.out.println(entree.readLine());
		        sortie.close();
		        entree.close();
			socket.close();
		}
		catch(FileNotFoundException exc) {
			System.out.println("Fichier introuvable");
		}
		catch(UnknownHostException exc) {
			System.out.println("Destinataire inconnu");
		}
		catch(IOException exc) {
			System.out.println("Probleme d'entree-sortie");
		}
	}
}
Le programme serveur est un peu plus long. La méthode principale crée un socket d'écoute puis, dans une boucle sans fin, utilise le socket d'écoute pour attendre des clients. Pour chaque client, le programme ouvre un socket de communication avec ce client puis instancie pour ce client la classe Service, que nous avons définie, destinée à rendre le service (recevoir un texte ligne par ligne et en compter les mots).

Pour permettre au serveur de gérer plusieurs clients à la fois, la classe Service étend la classe Thread et effectue le travail de réception des lignes de texte et de dénombrement des mots dans sa méthode run. Plus précisément, le constructeur de la classe Service ouvre un flux d'entrée et un flux de sortie sur le socket de communication puis démarre le thread ; durant la méthode run, le thread reçoit le texte ligne par ligne et utilise la classe StringTokenizer pour compter les mots ; lorsque la ligne reçue est *+*+*+*+, le nombre de mots est envoyé par le socket au client.

Les techniques de base relatives à la communication réseau sont en grande partie les mêmes que celles de la classe Client visible ci-dessus.


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.StringTokenizer;

class Service extends Thread {
	Socket socket;
	BufferedReader entree;
	PrintStream sortie;

	Service(Socket socket) {
		this.socket = socket;
		try {
			entree = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			sortie = new PrintStream(socket.getOutputStream());
			this.start();
		}
		catch(IOException exc) {
			try {
				socket.close();
			}
			catch(IOException e){}
		}
	}

	public void run() {
		String texte;
		int compteur = 0;

		try {
			while(!(texte = entree.readLine()).equals("*+*+*+*+")) 
                               compteur += (new StringTokenizer(texte, " ,.;:_-+*/\\.;\"'{}()=><\t!\n")).countTokens();
			sortie.println("votre texte possede " + compteur + " mots");
			sortie.close();
			entree.close();
			socket.close();
		}
		catch(IOException e) {}
	}
}

class Serveur {
	public static void main(String[] arg) {
		int portEcoute = 10302;
		ServerSocket standardiste;
		Socket socket;

		try {
			standardiste = new ServerSocket(portEcoute);
			while(true) {
				socket = standardiste.accept();
	 			new Service(socket);
			}
		}
		catch(IOException exc) {
	 		System.out.println("probleme de connexion");
		}
	}
    }
}
Si on lance la commande :
    java Serveur
sur la machine gargantua.enst.fr
puis si, sur la machine cliente, on lance, pour compter le nombre de mots du fichier Serveur.java, la commande :
    java Client Serveur.java gargantua.enst.fr alors, sur la machine cliente, on obtient :
    votre texte possede 125 mots

Vous pouvez accéder à :


portEcouteServeur = 10302 : numéro de port sur lequel le serveur attend les clients.
PrintStream : on ne peut pas utiliser ici un PrintWriter.
new Socket(arg[1], portEcouteServeur) : l'instance de la classe java.net.Socket ainsi construite permet de créer un socket pour une communication en mode connecté avec la machine dont le nom est indiqué en premier argument. Le second argument indique le port de la machine serveur sur lequel la demande de connexion de clients est attendue. De nombreux autres constructeurs existent pour cette classe. Ce constructeur lance une exception de type UnknownHostException si le nom de la machine correspond à une machine inconnue et une exception de type IOException si une erreur se produit au moment de la création du socket.
socket.getInputStream() : on obtient ainsi une instance de la classe InputStream qui servira à extraire du socket un flux d'octets vers le programme.
socket.getOutputStream() : on obtient ainsi une instance de la classe OutputStream qui servira à extraire du programme un flux d'octets vers le socket.
sortie.println("*+*+*+*+"); : on envoie au serveur le mot *+*+*+*+ pour indiquer la fin du texte.
System.out.println(entree.readLine()); : cette instruction permet de recevoir et d'écrire à l'écran le nombre de mots du texte.
socket.close(); : ne pas oublier cette instruction.
new StringTokenizer(texte, " ,.;:_-+*/\\.;\"'{}()=><\t!\n") : on instancie la classe StringTokenizer en indiquant dans la chaîne du second argument les caractères devant servir de séparateurs.
standardiste = new ServerSocket(portEcoute); : la classe ServerSocket modélise un socket d'écoute qui sert à recueillir les requêtes de connexion de clients.
socket = standardiste.accept(); : la méthode accept de la classe ServerSocket fait attendre le thread courant à l'écoute d'une requête de connexion et, quand une telle demande se présente, renvoie un socket, instance de la classe Socket, liée à un nouveau port de la machine, qui servira pour la communication avec le nouveau client.

© Irène Charon, Télécom ParisTech, 2011