Introduction à la programmation réseau

images/bluebelt.png

Principes généraux des échanges Client-Serveur sous TCP

image: client serveur TCP

  • L’application cliente demande d'établir un connexion avec une application serveur en demandant un numéro de port sur une machine. L’adresse de la machine est soit un adresse I.P. explicite comme 168.80.1.12 soit une adresse symbolique qui sera résolue par un annuaire en ligne (le DNS). Le numéro de port sera soit un numéro réservé "bien connu" -par exemple 80 pour http- soit un numéro spécifique à l’application et connu du client.
[Note]

Pour les numéros "bien connus" voir fichier /etc/services sous UNIX (on notera que cette description contient un numéro de port et un protocole sous-jacent: tcp pour les connexions permettant un dialogue, udp pour les messages asynchrones)

  • L’application serveur s’est appropriée un port sur sa machine (si possible!) et s’est mise à l'écoute sur ce port connu du client. A réception d’une demande de connexion elle établit une connexion avec le code client.
  • La suite des opérations dépend du mode de fonctionnement du serveur: il faut que celui-ci puisse répondre très rapidement à d’autres appels entrant. Donc soit il traite très rapidement la requête du client, soit il génère un Thread qui va s’occuper du dialogue avec l’application cliente et il se remet à l'écoute des demandes de connexions suivantes.
  • Avec le protocole TCP on a une garantie que les données seront transférées dans l’ordre. On va établir des flots d’entrée/sortie sur ce dispositif. Le flot en sortie du client sera connecté avec le flot d’entrée du serveur et vice-versa.
  • Les deux applications doivent convenir d’un protocole applicatif: quelles sont les éléments de l'échange (APDU), quelles sont les règles et scénarios du dialogue ?

Modalités d'établissement de la connexion en JAVA

Les objets Socket représentent les dispositifs qui permettent d'établir une connexion réseau.

  • Une application serveur va créer un objet ServerSocket qui va tenter de s’approprier un port sur la machine.
  • L’application serveur va ensuite invoquer successivement des appels à accept: c’est une appel bloquant qui rendra un objet Socket quand une connexion avec un client aura été obtenue.
  • Pour obtenir une connexion avec un serveur, l’application cliente va tenter directment d'établir une Socket (en demandant une adresse et un port).
  • Quand les Sockets sont interconnectées les deux applications peuvent demander l'établissement de Stream et faire des opérations d’entrée/sortie.

Un serveur TCP simple. 

import java.net.* ;
import java.io.* ;
import java.lang.System.Logger ;
import static java.lang.System.Logger.Level.* ;

public class Serveur {
   private ServerSocket srv ;
   // mettre ici le nom du package
   private System.Logger loggerGlobal = System.getLogger("com.maboite") ;

   public Serveur(int port) throws IOException{
      srv = new ServerSocket(port) ;
   }

   public void go() {
      while(true) {
         try (Socket sck = srv.accept() ) { // bloquant
            OutputStream os = sck.getOutputStream() ;
            DataOutputStream dos = new DataOutputStream(os) ;
            dos.writeUTF("Hello Net World!") ;
         } catch(IOException exc) {
            loggerGLobal.log(ERROR,"serveur",exc) ;
         }
      }
   }

   public static void main (String[] args) throws Exception{
      new Serveur(Integer.parseInt(args[0])).go() ;
   }
}

Dans cet exemple l'échange client/serveur est extrêmement simple : à la connexion c’est le serveur qui émet et qui prend ensuite l’initiative de couper la connexion.

Bien entendu d’autres possibilités sont ouvertes:

  • Le client pourrait être le premier à émettre (et donc le serveur devrait commencer par "lire")
  • La connexion pourrait rester ouverte pour un succession d'échanges entre client et serveur

Le client TCP correspondant. 

import java.net.* ;
import java.io.* ;
import java.lang.System.Logger ;
import static java.lang.System.Logger.Level.* ;

public class Client {
   private Socket sck ;
   // mettre ici le nom du package
   private System.Logger loggerGlobal = System.getLogger("global")  ;

   public Client(String host, int port) throws IOException{
      sck = new Socket(host, port) ;
   }

   public void go() {
      try (
         DataInputStream dis =
            new DataInputStream(sck.getInputStream()) ){
         String msg = dis.readUTF() ;
         loggerGlobal.info("reçu : " +msg) ;
      } catch(IOException exc) {
         loggerGlobal.log(ERROR,"serveur",exc) ;
      }
   }
   public static void main (String[] args) throws Exception{
      Client cli = new Client(args[0], Integer.parseInt(args[1]));
      cli.go() ;
   }
}

Ici le client ne se pose aucune question sur la durée de la communication puisque c’est le serveur qui prend l’initiative de couper la connexion.

On notera dans les deux exemples que les constructeurs sont susceptibles d'échouer (numéro de port déjà pris du coté du serveur, problème de désignation de l’hôte du serveur, accès à cet hôte, absence de processus serveur, etc. du coté du client).

--exercice--