1. Corrigé du TD numéro 1 : Communication via des sockets¶
1.1. Un exemple de Serveur Jouet Monoclient¶
1.1.1. Une petite classe¶
On peut utiliser le code Java suivant, on commence par quelques déclarations standard, les champs de Serveur_jouet sont statiques.
1 2 3 4 5 6 7 8 9 10 | import java.net.*;
import java.io.*;
import java.util.*;
public class Serveur_Jouet {
// FINISH string
private static final String Finish=""+(char) 4;; // caractère de fin ctrl-d
private static ServerSocket gestionnaire_de_connexion; // Objet gerant les sockets
private final static int port = 12000; /* Port d'écoute */
private static Socket ma_connection; /* file instanciée */
|
1.1.2. La creation du ServerSocket¶
Le main de la classe commence par créer le gestionnaire de sockets. Ici dès sa création l’objet gestionnaire_de_connexion qui est un ServerSocket va attendre et accepter les connexions internet sur le port 12000, les clients arrivants sont tous mis en attente en attendant que nous les traitions. Le ServerSocket rst donc un obket assez complexe géré par la JVM, il s’exécute en quelque sort en tâche de fond sans que nous ayons à nous en préoccuper.
Dans le bloc qui suit on crée juste le serveur de socket (que la jvm gère) .
public static void main(String[] args) {
/* Creation du gestionnaire de socket */
try{
gestionnaire_de_connexion = new ServerSocket(port);
System.out.println("Serveur Jouet lancé sur " + (port) );}
catch (IOException e) {
System.out.format(" Cannot create to the server, port %d may be busy\n", port);
System.exit(-1); }
1.1.3. L’accueil d’un Client¶
Maintenant on attend un client et on le sert.
- La ligne 4 est importante car elle est bloquante, elle signifie que l’on attend que le ServerSocket nous délivre un client via une Socket. Si aucun client n’est en attente dans le ServerSocket notre fil de calcul va attendre.
- Les lignes 12 et 13 permettent de lire ce qui arrive sur la socket, il s’agit de notre input. La ligne 12 convertit le flux d’octets retourné par getInputStream en flux de caractères tandis que la ligne 13 permet d’utiliser un tampon de lecture ce qui améliore les performances.
- La ligne 17 permet de récupérer un flux sortant: getOutputStream retourne un flux de sortie et on l’entoure d’une PrintWriter, on va ainsi pouvoir écrire sur la socket comme sur une sortie classique.
- La ligne 21 envoie un message au client, on utilise le flux de sortie exactement comme la sortie standard. L’opération est asynchrone.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | try{
// Attente d'une bloquante connexion
System.out.println("waiting for connexion") ;
ma_connection = gestionnaire_de_connexion.accept();
// Connection recupérée, on determine l'ip et le port
String c_ip = ma_connection.getInetAddress().toString() ;
int c_port= ma_connection.getPort();
System.out.format("client admis IP %s sur le port %d\n", c_ip, c_port);
// Recuperation du flux entrant la socket (lecture)
InputStreamReader isr = new InputStreamReader(ma_connection.getInputStream(), "UTF-8");
BufferedReader flux_entrant = new BufferedReader(isr) ;
System.out.println("Mon Tampon de lecture est attache ");
// Récupération de notre flux de sortie (écriture)
PrintWriter ma_sortie = new PrintWriter(ma_connection.getOutputStream() , true);
System.out.println("Mon Tampon pour ecrire attache ");
System.out.format("Pret à servir IP %s sur le port %d\n", c_ip, c_port);
ma_sortie.format("Hello %s sur le port %d, Ready!\n" , c_ip, c_port );
|
1.1.4. La lecture des données envoyées par le client¶
Il ne reste plus qu’à lire en boucle les données transmises par le client, on effectue ici une lecture Bloquante.
- La ligne 1 signifie que l’on attend et bloque jusqu’à ce que le flux d’entrée contienne une ligne, Attention cette ligne lit aussi les données et les place dans message_lu, enfin on quitte la boucle si la connexion est defectueuse.
- En ligne 3 on vérifie si le message est une requête de de fin de connexion
- En ligne 9 on affiche juste les données recues avec le numéro de ligne sur la sortie standard
1 2 3 4 5 6 7 8 9 10 11 | while ( (message_lu = flux_entrant.readLine()) != null ){
// Si le client demande de terminer
if (message_lu.contains(Finish) ){
// on termine proprement
System.out.format ("[%s] recu, Transmission finie\n",message_lu);
ma_sortie.println("Vous etes VIRE");
terminer();
}
System.out.format( "[%d]--> [%s]]\n", line_num, message_lu);
line_num++;
}
|
1.1.5. Un exemple de Code pour le serveur Monoclient.¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | import java.net.*;
import java.io.*;
import java.util.*;
public class Serveur_Jouet {
// FINISH string
private static final String Finish=""+(char) 4;;
private static ServerSocket gestionnaire_de_connexion; // Objet gerant les sockets
private final static int port = 12000; /* Port d'écoute */
/* Unique connecteur/socket du serveur */
private static Socket ma_connection; /* file instanciée */
public static void main(String[] args) {
/* Creation du gestionnaire de socket */
try{
gestionnaire_de_connexion = new ServerSocket(port);
System.out.println("Serveur Jouet lancé sur " + (port) );}
catch (IOException e) {
System.out.format(" Cannot create to the server, port %d may be busy\n", port); System.exit(-1); }
try{
// Attente d'une bloquante connexion
System.out.println("waiting for connexion") ;
ma_connection = gestionnaire_de_connexion.accept();
// Connection recupérée, on determine l'ip et le port
String c_ip = ma_connection.getInetAddress().toString() ;
int c_port= ma_connection.getPort();
System.out.format("client admis IP %s sur le port %d\n", c_ip, c_port);
/* On Associe un tampon pour lire sur le flux connection
Input streamreader permet de transformer le flux d'octets en flux de caracteres
le second argument et le type d'encodage des caractere --utf-8, isoXXXX etc ... */
InputStreamReader isr = new InputStreamReader(ma_connection.getInputStream(), "UTF-8");
// Une seconde encapsulation qui permet d'améliorer les perfomances en lisant par blocs -- pour les gros fichiers
BufferedReader flux_entrant = new BufferedReader(isr) ;
System.out.println("Mon Tampon de lecture est attache ");
// Stream de sortie, getOutputStream renvoie un Outputstream sur lequel on peut juste écrire des bit
// PrintWriter l'encapsule ce qui permet d'érire comme sur Sys
// le second parametre impose l option autoflush .. ce qui evite de faire de forcer l'envoi des messages partout
PrintWriter ma_sortie = new PrintWriter(ma_connection.getOutputStream() , true);
System.out.println("Mon Tampon pour ecrire attache ");
System.out.format("Pret à servir IP %s sur le port %d\n", c_ip, c_port);
ma_sortie.format("Hello %s sur le port %d, vous etes, pour faire simple, disons Admis\n" , c_ip, c_port );
String message_lu = new String();
int line_num =0 ;
/* On lit une ligne dans le flux_entrant La fonction readline est Bloquante
La condition du while fait diverses choses
elle attend que le client ai ecrit au moins une ligne
si la connection est brisée ou fautive ce message vaudra null et l'on quitera la boucle while
*/
while ( (message_lu = flux_entrant.readLine()) != null ){
// Si le client demande de terminer
if (message_lu.contains(Finish) ){
// on termine proprement
System.out.format ("[%s] recu, Transmission finie\n",message_lu);
ma_sortie.println("Vous etes VIRE");
terminer();
}
System.out.format( "[%d]--> [%s]]\n", line_num, message_lu);
line_num++;
}
// Si on est ici à priori le client à fermé la connection, sans envoyer finish (pex on peut tuer le processus telnet)
System.out.println( "Client deconnecté, je termine\n" ) ;
terminer();
}
catch (IOException e) {
System.err.println(" Erreur de reception");
e.printStackTrace(); terminer();}
}
private static void terminer(){
try{
if (ma_connection != null) ma_connection.close();
if (gestionnaire_de_connexion != null) gestionnaire_de_connexion.close();
}
catch (IOException e) {
e.printStackTrace();
}
System.exit(0);
}
}
|
1.2. Un Serveur Séquentiel¶
1.2.1. Principe¶
On va simplement ajouter une boule infinie autour du code précedent, que l’on organise un peu. La méthode main() se réduit à la création du serveur et à son exécution (les try catch la rende un brin verbeuse).
1.2.2. Méthode Main¶
Seules les lignes 10 et 19 font vraiment quelque chose, respectivement : la ligne 10 crée le serveur et la ligne 19 l’exécute.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public static void main(String[] args) {
/* On crée puis lance le serveur */
Serveur_Ameliore Mon_serveur = null;
if (args.length != 1) {
System.err.println("usage: java "+ Serveur_Ameliore.class.getCanonicalName()
+ " serverPort");
System.exit(-1);
}
try {
Mon_serveur = new Serveur_Ameliore(Integer.parseInt(args[0]));
} catch (NumberFormatException e) {
System.out.println("Format du port incorrect \n: format exception for "
+ e.getMessage());
System.exit(-1);
} catch (IOException e) {
System.out.println("Impossible de créer le socket server : " + e);
System.exit(-1);
}
Mon_serveur.run();
}
|
1.2.3. La Méthode run()¶
Dans cette méthode le serveur se contente de :
- D’attendre et d’accepter un client ligne 6).
- Puis de servir le client (ligne 16).
- De recommencer à l’infini (ligne 3)
Ce sont les vérifications et les message pour l’utilisateur qui rende le code long car au fond il se limite à 3 instructions.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public void run() {
Socket ma_connection = null;
while (true) {
try { // Attente bloquante connexion
ma_connection = this.mon_connecteur.accept();
} catch (IOException e) {
System.out.println("Impossible de détacher une socket : " + e);
System.exit(-1);
}
int c_port = ma_connection.getPort();
String c_ip = ma_connection.getInetAddress().toString();
System.out.format("Un client est arrivé avec IP : %s sur le port %d\n", c_ip,c_port);
/* On traite le client que l'on a associé */
try {
Service_Client(ma_connection);
} catch (IOException e) {
System.out.println("Erreur de Service Client : " + e);
System.exit(-1); }
}
}
|
1.2.4. La Méthode Service_Client()¶
Dans cette méthode le serveur reçoit les données envoyées par le client en les lisant ligne à ligne de façon bloquante.
Le code se résume à quelques opérations :
- En lignes 3-5 on récupère de flux entrant de données.
- En ligne 7 on récupère le flux de sortie.
- En lignes 12-13 on déclare un compteur de ligne et une chaine où stocker le message du client.
- En ligne 20 on envoie un message au client, l’opération n’est pas bloquante et on ne sait pas si le client la reçoit.
- Le while de la ligne 21 contient une instruction bloquante – cf le serveur jouet –
- Le If de la ligne 25 permet de tester si le client à demandé de terminer la transmission, si oui on clot la socket (ligne 30) et on termine en quittant la méthode de service (ligne 31).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | private Boolean Service_Client(Socket la_connection) throws IOException {
/* On Associe une file d'entrée a la connection */
InputStreamReader isr = new InputStreamReader(la_connection.getInputStream());
/* On transforme cette file en file avec tampon */
BufferedReader flux_entrant = new BufferedReader(isr);
System.out.println("Tampon entree attache ");
PrintWriter ma_sortie = new PrintWriter(la_connection.getOutputStream(), true);
System.out.println("Sortie attachée");
System.out.println("Prêt à servir le Client : "+ la_connection.getRemoteSocketAddress());
String clientName = la_connection.getRemoteSocketAddress().toString();
String message_lu = new String();
int line_num = 0;
/*
* On lit le flux_entrant ligne à ligne ATTENTION : La fonction readline
* est Bloquante readline retourne null si il y a souci avec la
* connexion On s arrete aussisi connexion_non_terminee est vraie
*/
ma_sortie.format("Bonjour %s j attends tes données \n",clientName);
while ( (message_lu = flux_entrant.readLine()) != null)) {
System.out.format("%d: -> [%s]\n", line_num, message_lu);
line_num++;
/* si on recoit Finish on clot et annonce cette terminaison */
if (message_lu.contains(Finish)) {
System.out.println("Reception de " + Finish
+ " -> Transmission finie");
// On ferme la connection
System.out.format("Je clos la connection %s :\n",clientName);
terminer(la_connection);
return (true);
}
}
return false;
|
1.2.5. Exemple de Code pour le Serveur Amélioré¶
Ici c’est juste le récapitulatif.
package td1;
/* On importe les classes Reseau, Entrees Sorties, Utilitaires */
import java.net.*;
import java.io.*;
public class Serveur_Ameliore {
ServerSocket mon_connecteur; // serveur de socket du serveur amélioré
/* Port d'écoute */
private int port;
final String Finish = "" + (char) 4; //Signal de fin de connection aussi nommé EOT ctrl-d
public Serveur_Ameliore(int cport) throws IOException {
port = cport;
this.mon_connecteur = new ServerSocket(port); //Creation du gestionnaire de socket
System.out.format("Serveur Amélioré lancé sur le port %d\n", port);
}
private void terminer(Socket ma_connection){
if (ma_connection != null)
{
try {
ma_connection.close();
System.out.format("Socket fermee \n");
}
catch ( IOException e ) { System.out.println("weird, nawak .... \n ");} // do nothiing }
}}
public void run() {
Socket ma_connection = null; // file instanciée pour commmuniquer avec le client
while (true) {
// // /* Attente bloquante connexion */
try {
ma_connection = this.mon_connecteur.accept();
} catch (IOException e) {
System.out.println("Impossible de détacher une socket : " + e);
System.exit(-1);
}
int c_port = ma_connection.getPort();
String c_ip = ma_connection.getInetAddress().toString();
System.out.format("Un client est arrivé avec IP : %s sur le port %d\n", c_ip, c_port);
/* On traite le client que l'on a associé */
try {
Service_Client(ma_connection);
} catch (IOException e) {
System.out.println("Erreur de Service Client : " + e);
System.exit(-1); }
}
}
private Boolean Service_Client(Socket la_connection) throws IOException {
/* On Associe une file d'entrée a la connection */
InputStreamReader isr = new InputStreamReader(la_connection.getInputStream());
/* On transforme cette file en file avec tampon */
BufferedReader flux_entrant = new BufferedReader(isr);
System.out.println("Tampon entree attache ");
// On récupère la file de sortie
PrintWriter ma_sortie = new PrintWriter(la_connection.getOutputStream(), true);
System.out.println("Sortie attachée");
String clientName = la_connection.getRemoteSocketAddress().toString();
System.out.format("Prêt à servir le Client %s\n", clientName);
String message_lu = new String();
int line_num = 0;
/*
* On lit le flux_entrant ligne à ligne ATTENTION : La fonction readline
* est Bloquante readline retourne null si il y a souci avec la
* connexion On s arrete aussisi connexion_non_terminee est vraie
*/
ma_sortie.format("Bonjour %s j attends tes données \n",clientName);
while ((message_lu = flux_entrant.readLine()) != null) {
System.out.format("%d: -> [%s]\n", line_num, message_lu);
line_num++;
/* si on recoit Finish on clot et annonce cette terminaison */
if (message_lu.contains(Finish)) {
System.out.println("Reception de " + Finish
+ " -> Transmission finie");
// On ferme la connection
System.out.format("Je clos la connection %s :\n",clientName);
terminer(la_connection);
return (true);
}
}
return false;
}
public static void main(String[] args) {
/* On crée puis lance le serveur */
Serveur_Ameliore Mon_serveur = null;
if (args.length != 1) {
System.err.println("usage: java "+ Serveur_Ameliore.class.getCanonicalName()
+ " serverPort");
System.exit(-1);
}
try {
Mon_serveur = new Serveur_Ameliore(Integer.parseInt(args[0]));
} catch (NumberFormatException e) {
System.out.println("Format du port incorrect \n: format exception for "
+ e.getMessage());
System.exit(-1);
} catch (IOException e) {
System.out.println("Impossible de créer le socket server : " + e);
System.exit(-1);
}
Mon_serveur.run();
}
}
1.3. Un client Minimaliste¶
Le client est semblable au serveur, nous donnons un exemple simplifié où le client se contente de se connecter et d’envoyer les données saisies au clavier vers le serveur.
Objet client minimaliste se connectant en local sur le port 8500.
public class Minimal { /* define the client class */
private String hote = "127.0.0.1";
private int port = 8500;
private Scanner console_input;
public Minimal() {}
Méthode execute() qui se connecte au serveur, lit des données sur l’entrée standard et les envoie au serveur.
- La ligne 5 crée et ouvre la socket.
- La ligne 11 est le début d’une boucle infinie qui lit les données sur la console et les envoue au serveur.
- La line 12 lit une ligne sur l’entrée standard.
- La ligne 13 envoie cette donnée au serveur.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public void execute() {
console_input = new Scanner(System.in);
Socket laConnection = null;
try {
laConnection = new Socket(this.hote, this.port);
PrintWriter ma_sortie = new PrintWriter(
laConnection.getOutputStream(), true);
System.out.format(" Contacting %s on %d\n", hote ,port);
ma_sortie.println("Hello je suis : spammeur");
System.out.println("entrer les données");
while (true) {
String data = console_input.next();
ma_sortie.println(data);
if (data.equals("end")){
System.out.println("termine");
laConnection.close();
System.exit(0);
}
} catch (IOException e) {
System.out.format("Probleme de connection avec serveur fontionne : %s",e);
System.exit(-1);
}
|
Au final cela donne la classe suivante :
import java.net.*;
import java.io.*;
import java.util.Scanner;
public class Minimal { /* define the client class */
private String hote = "127.0.0.1";
private int port = 8500;
private Scanner console_input;
public Minimal() {}
public void execute() {
console_input = new Scanner(System.in);
Socket laConnection = null;
try {
laConnection = new Socket(this.hote, this.port);
PrintWriter ma_sortie = new PrintWriter(
laConnection.getOutputStream(), true);
System.out.format(" Contacting %s on %d\n", hote ,port);
ma_sortie.println("Hello je suis : spammeur");
System.out.println("entrer les données");
while (true) {
String data = console_input.next();
ma_sortie.println(data);
if (data.equals("end")){
System.out.println("termine");
laConnection.close();
System.exit(0);
}
}
} catch (IOException e) {
System.out.format("Probleme de connection avec serveur fontionne : %s",e);
System.exit(-1);
}
}
public static void main(String[] args) {
Minimal test = new Minimal();
test.execute();
}
}
1.4. Un Client plus élaboré¶
Au final cela donne la classe suivante :
import java.net.*;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Scanner;
public class Client { /* define the client class */
private final String hote;
private final int port;
private final String id;
private Scanner console_input;
final String Finish = "" + (char) 4; //Signal de fin de connection aussi nommé EOT ctrl-d
byte[] datas=null;
private String fileName;
public Client(String hote, int port, String mon_id) {
this.hote = hote;
this.port = port;
this.id = mon_id;
}
private void getdata() {
Boolean wait_for_file = true;
console_input = new Scanner(System.in);
while (wait_for_file) {
System.out.println("Entrez le nom du ficher à envoyer");
fileName = console_input.next();
System.out.println("Opening " + fileName);
try {
Path file_path = Paths.get(fileName);
datas = Files.readAllBytes(file_path);
System.out.println(datas.length + " Bytes Read ");
return;
} catch (java.nio.file.InvalidPathException e) {
System.out.println("path is incorrect");
} catch (java.nio.file.NoSuchFileException e) {
System.out.format("Absence du fichier %s\n", fileName);
continue;
} catch (IOException e) {
System.out.println("Cannot read data, is the file there ? : "
+ e);
continue;
}
}
}
public void execute() {
Socket la_connection = null;
OutputStream os;
PrintWriter ma_sortie;
try {
la_connection = new Socket(this.hote, this.port);
} catch (IOException e) {
System.out.format("Probleme de connection avec le serveur %s\n",e);
System.exit(-1);}
try {
os= la_connection.getOutputStream();
ma_sortie = new PrintWriter(os, true);
System.out.println(" Contacting " + this.hote + " on " + this.port);
ma_sortie.println("Hello je suis :" + this.id);
getdata();
os.write(datas, 0, datas.length);
System.out.println("Données envoyées, envoi de la terminaison");
ma_sortie.println(Finish);
}
catch (IOException e) {
System.out.println("data not fully transmited : " + e);}
}
public static void main(String[] args) {
if (args.length != 3) {
System.err.println("Il me faut 3 arguments: hote port identifiant");
System.exit(1);}
try {
new Client(args[0], Integer.parseInt(args[1]), args[2]).execute();}
catch (NumberFormatException e) {
System.out.format("Mauvais format du port\n %s\n", e.getMessage());
System.exit(-1);
}
}
}