next up previous contents index
Next: 40 Appel de méthodes Up: Java: Programmation avancée Previous: Java: Programmation avancée

Subsections

39 Programmation réseau

 

39.1 Introduction aux réseaux

Ce chapitre passe en revue, très rapidement, les quelques notions nécessaires pour la programmation réseau.

39.1.1 Généralités

     

Un réseau informatique est constitué d'un ensemble d'ordinateurs et de périphériques interconnectés. Un noeud du réseau qui correspond à un ordinateur est généralement appelé hôte .

TCP/IP est l'architecture réseau la plus répandue. Contrairement à ce que son nom laisse croire, TCP/IP n'est pas une architecture réduite à deux protocoles : IP (Internet Protocol) et TCP (Transmission Control Protocol). L'architecture TCP/IP inclut de nombreux "services d'application", des protocoles élaborés et complexes, sur lesquels les applications distribuées peuvent s'appuyer. Sans rentrer dans les détails, le schéma suivant précise les divers couches du réseau où interviennent les protocoles IP, TCP, et UDP.


Les couches réseau

Les couches les plus basses, Ligne, Physique, et Réseau ne sont pas incluses dans l'architecture TCP/IP. En fait, TCP/IP s'appuie sur n'importe quelles couches basses existantes :

Protocole IP

Le protocole IP ne se préoccupe que de routage et contrôle dans un environnement "inter-réseau". Le protocole IP permet l'échange de datagrammes (en mode non connecté), entre des hôtes connectés à des réseaux physiques divers. Le protocole IP (Internet Protocol ) est un protocole ouvert et indépendant du système. Des noeud d'un réseau sont potentiellement reliés par plusieurs chemins. Chaque paquet suit un chemin particulier mais une succession de paquets provenant un même bloc de données ne suivent pas forcément le même chemin.

Le protocole IP comporte des limitations notables :

Les applications désirant communiquer via IP disposent de deux alternatives :

Protocole TCP

Le protocole TCP est un protocole plus sûr bâti au dessus du protocole IP. Le rôle de TCP est de sécuriser l'envoi et la réception des données par un système d'accusé de réception. . C'est un protocole de type "orienté connexion", c'est à dire que les échanges de données ont lieu de façon ordonnée, fiable, après négociation préliminaire des caractéristiques de transfert. En particulier, le protocole TCP garde de lui-même une copie des données émises tant que celles-ci n'ont pas été acquittées. TCP utilise également un mécanisme de checksum pour garantir l'intégrité des données, ainsi que des numéros de séquence assurant la réception ordonnée des données Lorsque des paquets sont perdus, TCP demande la réexpédition des ces derniers. Il reconstitue également les paquets suivant l'ordre de leur expédition. TCP est donc un protocole plus fiable mais bien moins économique.

Protocole UDP

Le protocole UDP (User Datagram Protocol) est un protocole de la couche de transport au dessus de IP. Le protocole UDP est essentiellement utilisable lorsque les applications désirant échanger des données privilégient la rapidité et la simplicité par rapport à la fiabilité. Le protocole UDP ne numérote pas les données envoyées, n'acquitte pas les données reçues, et propose un checksum optionnel portant sur les données Avec ce protocole, rien ne garantit la livraison effectif d'un paquet. De plus, lorsque les différents paquets d'un même bloc de données arrivent à destination rien ne garantit l'ordre d'arrivée de ces paquets.

Il existe des applications toutes les données sont vitales et aucune perte de données n'est tolérable; dans ces cas, on choisira d'utiliser le protocole TCP. Par contre, il existe d'autres applications ou un perte de quelques données est sans conséquence: dans ces, on utilisera plutôt le protocole UDP.

Adresse internet

  Chaque noeud du réseau possède une adresse internet composée d'une série d'octets. L'attribution d'une adresse à un noeud du réseau dépend du type de réseau. Pour ce qui nous concerne, nous pouvons ignorer la manière dont ces adresses sont attribués aux noeuds du réseau. Par exemple, l'adresse de la machine du serveur du département GBM de l'ESIL est constitué des octets 139, 124, 32 et 249 que l'on a l'habitude de noter sous la forme 139.124.32.249.

Parallèlment aux adresses, les noeuds possèdent un nom plus parlant pour l'être humain. Le serveur du département GBM de l'ESIL ne nomme gbm.esil.univ-mrs.fr. Ce nom est totalement indépendant de l'adresse internet ce qui permet de changer l'adresse internet d'une machine sans devoir forcément changer son nom.

DNS

  Le DNS (Domain Name System) permet la mise en correspondance des adresses internet et des noms de noeuds.

  Les données qui circulent dans un réseau sont découpés en tronçons d'une certaine taille; ces tronçon sont appelés paquets .

Les ordinateurs communiquent entre eux à travers un protocole: il s'agit d'une convention que reconnaît les machines émettrices et les machines réceptrices. Un exemple typique d'une protocole est le protocole HTTP qui définit les modalités de communication entre un navigateur WEB et un serveur de pages HTML. Il existe bien d'autres protocoles: file, ftp, gopher, news, telnet, WAIS, etc.

Architecture Client/Serveur

  Le mode de communication qu'un hôte établit avec un autre hôte qui fournit un service quelconque est désigné par le terme Client/Serveur . Les notions de base de l'architecture client/serveur sont:


Dialogue Client/Serveur

Port UDP et TCP

    Chaque hôte est susceptible de fournir plusieurs services simultanément : serveur HTTP, FTP, MAIL, etc. Selon le type de service que l'on veut utiliser, une machine cliente s'adressera à une machine serveur sur un canal dédié; on appelle ports logiques ces canaux.

Chaque machine sous IP possède quelques 65 535 ports; par exemple, le service HTTP est lié au port 80, le service SMTP (courrier électronique) est relié au port 25, etc.

Une machine cliente qui s'adresse à une machine serveur le fait en spécifiant son adresse internet et le numéro de port qui l'intéresse.

Une différence importante au niveau de l'utilisation des ports TCP et UDP est la suivante

Avec TCP, il sera aisé de concevoir des applications serveur "multi-processus" : chaque processus sera en charge de gérer une relation client-serveur, via une connexion TCP; chaque processus possède une file d'attente propre. Avec UDP, c'est impossible : la même file d'attente sera utilisée par tous les processus.

Avec UDP, les seules applications serveur qui pourront fonctionner en version "multi-processus" seront celles conçues pour accepter une requête et renvoyer immédiatement une réponse, et une seule. C'est le cas de nombreuses applications dites " transactionnelles" : les requêtes sont sérialisées par le port UDP; chacune est traitée par un processus, la réponse est ensuite envoyée. Les requêtes consécutives ne doivent pas être corrélées.

Ports Réservés

Les applications serveurs nécessitent des ports connus de façon globale : un port de serveur est équivalent à un numéro de téléphone publié dans un annuaire.

Avec TCP et UDP, un certain nombre de ports sont réservés à des services bien connus. Par exemple, sur TCP :

Sur UDP :

Ces ports alloués statiquement, aussi appelés Well Known Ports , correspondant à des Well Known Services (WKS) , dans un espace de port réservé au serveur :

Sous Unix, les ports réservés pour les "services bien connus" sont visibles dans le fichier /etc/services. Par exemple :

 
tcpmux          1/tcp           # TCP port multiplexer (RFC 1078)
echo            7/tcp
echo            7/udp
ftp-data        20/tcp
ftp             21/tcp
telnet          23/tcp
smtp            25/tcp          mail
domain          53/tcp          nameserver      # name-domain server
domain          53/udp          nameserver
bootp           67/udp          bootps      # bootp server
bootpc          68/udp                      # bootp client
tftp            69/udp
finger          79/tcp
iso-tsap        102/tcp
snmp            161/udp
snmp-trap       162/udp         snmptrap
xdmcp           177/udp         # X Display Mgr. Control Prot.
route           520/udp         router routed

39.3 Les URLs

  On appelle URL (Uniform Resource Location ) la manière de nommer une ressource sur le réseau Internet. Par exemple, http://gbm.esil.univ-mrs.fr/eleves/index.html désigne la page WEB d'accueil des élèves du département GBM de l'ESIL. Un URL se compose de trois parties: le protocole, l'hôte et le document. La syntaxe générale d'un URL est :
<protocole>://<nom_hote>[:<port>]/<chemin>/<nom_fichier>#<section>

Tout comme pour les fichiers dans un système de fichiers, une ressource internet peut être spécifié soit par son URL absolu soit par son URL relatif. Un URL relatif désigne une ressource qui peut être désigné relativement au document courant. Les URL relatifs ne peuvent référencer que des ressources qui se trouvent dans le même hôte que le document où figure l'URL.

Le protocole HTTP

   

Le protocole de communication HHTP consiste :

1.
établir une connexion TCP avec le serveur sur le port dédié (80 par défaut, ou bien le port spécifié dans l'URL).
2.
émettre la requête d'extraction du document spécifié par l'URL
3.
Réception de la réponse
4.
clôture de connexion

 

A TERMINER

Le protocole MIME

 

A TERMINER

CGI

 

Les CGI (Commun Gateway Interface ) sont des programmes qui résident et s'exécutent sur le serveur. ces programmes sont déclenchés en réponse à la demande d'un client à travers une page HTML. C'est une manière de réaliser des pages WEB dynamiques.

Comme ce sont des programmes qui résident sur le serveur et qui s'exécutent sur le serveur, il n'y a aucune restriction quant au choix du langage utilisé. Dans la pratique, on constante que les CGI sont, le plus souvent, des programmes réalisés en C, Perl ou AppleScript; même s'il est tout à fait possible de réaliser des CGI en Java, C++, Pascal, Prolog, Lisp, et tout autre langage de programmation.

Le package java.net

Tout ce qui concerne la programmation réseau est contenu dans le package java.net excepté les manipulations spécifiques aux applets que l'on trouve dans le package java.applet.

la classe java.net contient les classes

et les interfaces

39.2 Adressage IP

   

Comme nous l'avons dit, un hôte sur le réseau peut être nommé soit par son adresse internet soit son nom DNS; leur manipulation se fait par la classe java.net.InetAddress.

 
public final class InetAddress extends Object implements Serializable {
public boolean isMulticastAddress()
public String getHostName()
public byte[] getAddress()
public String getHostAddress()
public int hashCode()
public boolean equals(Object obj)
public String toString()
public static InetAddress getByName(String host) throws UnknownHostException
public static InetAddress[] getAllByName(String host) throws UnknownHostException
public static InetAddress getLocalHost() throws UnknownHostException
}

Cette classe ne possède pas de constructeur. En général, les instances de InetAddress s'obtiennent comme résultat des méthodes static: getByName, getAllByName et getLocalHost.

 public static InetAddress getByName (String host) throws UnknownHostException

Retourne un objet de la classe InetAddress codant l'adresse IP d'un hôte. L'argument host est le nom DNS de l'hôte ou son adresse IP.

 public static InetAddress[] getAllByName (String host) throws UnknownHostException

Retourne un tableau d'objets de la classe InetAddress codant toutes les adresses IP d'un hôte. L'argument host est le nom DNS de l'hôte ou son adresse IP.

 public static InetAddress getLocalHost () throws UnknownHostException

Retourne un objet de la classe InetAddress codant l'adresse IP du hôte local.

 public String getHostName ()

Retourne une chaîne de caractères contenant le nom d'hôte de l'objet InetAddress ou son adresse IP (si le nom n'existe pas).
 public byte[] getAddress ()
Retourne un tableau d'octet contenant les 4 octets de l'adresse IP de l'objet InetAddress.
 public String getHostAddress ()
Retourne une chaîne de caractères contenant les 4 octets de l'adresse IP de l'objet InetAddress; cette chaîne est de la forme "%d.%d.%d.%d".

 

   import java.net.*;

   public class Adresses {
      public static void main(String[] args) {
         for(int i=0 ; i< args.length; i++) {
            try {
               InetAddress[] inetadrs = InetAddress.getAllByName(args[i]);
               System.out.println(args[i] + ":");
               for(int j=0 ; j<inetadrs.length ; j++) System.out.println(inetadrs[j]);
            }
               catch (UnknownHostException e) { System.out.println(args[i] + " inconnu !!! "); }
         }
      }
   }

 

Applet

Les URLs

   

Comme nous l'avons déjà dit, une URL désigne une ressource dans une réseau sous le format suivant:

protocole://nom_hote[:port]/chemin/nom_fichier#section

En Java , l'URL n'est pas représenté par une chaîne de caractères mais par une instance de la classe java.net.URL

 

   public final class URL extends Object implements Serializable, Comparable {
      public URL(String protocol, String host, int port, String file) throws MalformedURLException
      public URL(String protocol, String host, String file) throws MalformedURLException
      public URL(String spec) throws MalformedURLException
      public URL(URL context, String spec) throws MalformedURLException
      protected void set(String protocol, String host, int port, String file, String ref)
      public int getPort()
      public String getProtocol()
      public String getHost()
      public String getFile()
      public String getRef()
      public boolean equals(Object obj)
      public int hashCode()
      public int compareTo(Object url)
      public boolean sameFile(URL other)
      public String toString()
      public String toExternalForm()
      public URLConnection openConnection() throws IOException
      public final InputStream openStream() throws IOException
      public final Object getContent() throws IOException
      public static void setURLStreamHandlerFactory(URLStreamHandlerFactory fac)
   }

La classe URL dispose de quatre constructeurs:

Tous ces constructeurs lève l'exception MalformedURLException lorsque le protocole spécifié est inconnu. Vous aurez noté qu'une instance de URL est une sorte de constante puisque la classe URL ne fournit aucune méthode pour modifier le protocole ou l'hôte, etc.

Par contre, il est possible de consulter les attributs d'une URL. Les méthodes getPort, getProtocol, getHost, getFile et getRef permettent de récupérer le port, le protocole, l'hôte, le fichier et la section du fichier.

Pour illustrer tout ceci, voici un exemple provenant du tutorial de JDK:

 
import java.net.*;
import java.io.*;

public class ParseURL {
    public static void main(String[] args) throws Exception {
        URL aURL = new URL("http://java.sun.com:80/docs/books/tutorial/intro.html#DOWNLOADING");
        System.out.println("protocol = " + aURL.getProtocol());
        System.out.println("host = " + aURL.getHost());
        System.out.println("filename = " + aURL.getFile());
        System.out.println("port = " + aURL.getPort());
        System.out.println("ref = " + aURL.getRef());
    }
}
et voici le résultat de l'exécution de ce programme:
 
protocol = http
host = java.sun.com
filename = /docs/books/tutorial/intro.html
port = 80
ref = DOWNLOADING

Pour récupérer des données à partir d'une URL, la classe URL fournit les méthodes openConnection, openStream et getContent:

 public URLConnection openConnection () throws IOException

Retourne une instance de la classe URLConection qui représente une connexion vers vers l'objet désigné par l'url. Ceci peut sembler un peu mystérieux pour le moment; tout ceci devrai s'éclaircir un peu plus loin.
 public final Object getContent () throws IOException
Retourne le contenu de l'url. C'est un raccourci pour openConnection().getContent()
 public final InputStream openStream () throws IOException
Cette méthode établit la communication entre le client et le serveur en ouvrant un InputStream . Equivalent à openConnection().openStream().

 

   import java.io.*;
   import java.net.*;

   public class OpenURL {
      public static void main(String[] args) {
         BufferedReader in  = null;
         try {
            if (args.length != 1) {
               System.out.println("usage: java OpenURL <URL>");
               System.exit(0);
            }
            URL url = new URL(args[0]);
            in = new BufferedReader(new InputStreamReader(url.openStream()));

            String s;
            while ( (s=in.readLine()) != null) System.out.println(s);
         }
            catch (IOException e) {System.out.println("Erreur1");}
            catch (Exception e) {System.out.println("Erreur2");}
            finally {
               try { if (in != null) in.close();  }
                  catch (Exception e) {System.out.println("Erreur3");}
            }
      }
   }

et voici une partie de l'affichage produit:

 
Java OpenURL http://gbm.esil.univ-mrs.fr/~tourai/index.html 


 Touraïvane



...


...

 public boolean sameFile (URL other)

 

A TERMINER

 public String toString ()
 

A TERMINER

 public String toExternalForm ()
 

A TERMINER

 public static void setURLStreamHandlerFactory (URLStreamHandlerFactory fac)
 

A TERMINER

39.4 URLEncoder

39.5 Les sockets

 

TCP/IP offre, en particulier, une interface dite sockets pour l'écriture d'applications communicantes. Les sockets a été introduit avec le système d'exploitation UNIX de Berkeley. L'idée des sockets est de faire en sorte que deux programmes sur des hôtes différents puissent communiquer l'un avec l'autre à travers sans se soucier des détails de bas niveau de la communication réseau.

Les primitives TCP pour les sockets permettent :

Les sockets ne sont pas des objets spécifiquement réseaux, et encore moins spécifiquement TCP/IP. Ce sont des objets génériques qui doivent être paramétrés lors de leur création selon l'utilisation prévue :

Dans la communication par sockets, il existe toujours une machine qui joue le rôle du serveur et l'autre (ou les autres) qui joue le rôle de client.

Le serveur est une application qui tourne sur un hôte et qui est à l'écoute des requêtes d'un ou de plusieurs clients sur un port particulier.

Le client est une application qui tourne un hôte (pas forcément différent de celui sur lequel tourne le serveur). Il doit connaître l'hôte et le port sur lequel le serveur est à l'écoute; le client tente alors une connexion au serveur sur l'hôte et le port approprié.

Lorsque le serveur est conçu pour communiquer avec plusieurs clients, quand tout se passe bien,

Le client et le serveur peuvent alors communiquer l'un avec l'autre en écrivant et en lisant sur les sockets.

Les programmes serveurs doivent toujours être actifs lorsque des clients initient une connexion. Il existe deux façons d'assurer que ces serveurs soient activés :

Activation "manuelle"

Le lancement manuel signifie que le programme serveur s'exécute en permanence, même s'il n'est pas appelé par des programmes clients. Compte tenu du grand nombre de services réseaux disponibles sur une machine donnée, de nombreux processus, oisifs en attente mais consommant du temps CPU et des ressources, existent sur le système. Cette solution est peu recommandée.

Utilisation de INETD sous UNIX

La deuxième solution est l'utilisation d'un serveur particulier, le démon inetd, qui réalise les attentes pour le compte d'autres programmes serveurs. De plus, l'utilisation du réseau par un démon lancé par inetd est cachée : le programmeur utilise les descripteurs de fichiers correspondant à l'entrée et à la sortie standard du programme. La redirection de ces deux descripteurs vers un socket est réalisée de façon implicite et systématique par le processus inetd lui-même.

Le fichier /etc/inetd.conf

Le démon inetd, lors de son activation, consulte un fichier de configuration décrivant les services à assurer sur le système. Le fichier s'appelle /etc/inetd.conf, et contient un ensemble de lignes au format suivant :
 
service       stream  tcp      nowait  root    /usr/etc/progd  progd
Une telle ligne signifie que service est implanté sur cette machine, que les sockets utilisés sont de type stream, que le protocole utilisé est tcp, que le programme à activer lors d'une demande de service est /usr/etc/progd, que le nom à donner au programme sera progd, que le programme doit s'exécuter avec comme propriétaire root.

Le paramètre nowait indique que plusieurs instances de ce programme peuvent être activées sans attendre la terminaison des instances précédantes.

Le fichier /etc/services

Les ports à utiliser pour les sockets (ici les sockets TCP) sont définis dans un fichier particulier, /etc/services, qui contiendra la ligne suivante :
 
 service       78/tcp   alias_service
Cette ligne indique que pour le service défini dans le fichier inetd.conf, il faut utiliser le port 78 du protocole tcp. Le reste de la ligne est un alias possible pour le nom du service. Une fois ces informations récupérées, le serveur inetd va créer un socket, lui associer le numéro de port, et se mettre en attente d'une indication d'établissement de connexion à ce port. Comme de nombreux services existent, le superserveur inetd crée de nombreux sockets, assigne les numéros de port adéquats, se met en attente, etc..

Cas de services UDP

Dans le cas de service utilisant udp, le fichier inetd.conf contient des informations légèrement différentes :
 
service       dgram   udp     wait     root    /usr/etc/progd         progd
Le paramètre dgram identifie un socket de type datagramme, udp indique que le protocole utilisé est UDP, wait indique qu'il faut attendre la terminaison d'une instance du service avant de pouvoir en lancer la suivante. Notons que si les programmes serveurs sont conçus pour s'exécuter en parallèle (voir chapitre 5), on peut trouver la ligne suivante dans le fichier /etc/inetd.conf :
 
service       dgram   udp     nowait   root    /usr/etc/progd         progd
Dans les deux cas, on devra trouver la ligne correspondante dans le fichier /etc/services :
 
service       78/udp  alias_service

39.6 Connexion TCP et sockets

Java fournit deux classes pour la gestion des sockets:

39.6.1 Le client TCP

 

   public class Socket {
      protected Socket()
      protected Socket(SocketImpl impl) throws SocketException
      public Socket(String host, int port) throws UnknownHostException, IOException
      public Socket(InetAddress address, int port) throws IOException
      public Socket(String host, int port, InetAddress localAddr, int localPort) throws IOException
      public Socket(InetAddress address, int port, InetAddress localAddr, int localPort) throws IOException
      public Socket(String host, int port, boolean stream) throws IOException
      public Socket(InetAddress host, int port, boolean stream) throws IOException
      public InetAddress getInetAddress()
      public InetAddress getLocalAddress()
      public int getPort()
      public int getLocalPort()
      public InputStream getInputStream() throws IOException
      public OutputStream getOutputStream() throws IOException
      public void setTcpNoDelay(boolean on) throws SocketException
      public boolean getTcpNoDelay() throws SocketException
      public void setSoLinger(boolean on, int val) throws SocketException
      public int getSoLinger() throws SocketException
      public void setSoTimeout(int timeout) throws SocketException
      public int getSoTimeout() throws SocketException
      public void close() throws IOException
      public String toString()
      public static void setSocketImplFactory(SocketImplFactory fac) throws IOException
   }

Une application cliente doit ouvrir une connexion sur en créant un socket.

 
   try {
      socket sock = new Socket("gbm.esil.univ-mrs.fr", 2000);
   }
   catch (UnknoxnHostException e) { System.out.println("Hôte inconnu"); System.exit(-1); }
   catch (IOException e) { System.out.println("Erreur lors de connexion"); System.exit(-1);}
La connexion établie, les canaux d'entrée/sortie s'obtiennent grâce aux méthode getInputStream et getOutputStream.

Pour tester le client, mous allons utiliser une application serveur qui existe sur tous les systèmes UNIX et qui est à l'écoute du port 7: il s'agit du serveur echo qui se contente de renvoyer au client la chaîne que ce dernier lui envoie.

 


   import java.io.*;
   import java.net.*;
   public class Client {
      public static void main(String[] args) throws Exception, NumberFormatException {
         Socket serveur = new Socket("gbm.esil.univ-mrs.fr", Integer.parseInt(args[0]));
         PrintWriter Sout = new PrintWriter(serveur.getOutputStream());
         BufferedReader Sin = new BufferedReader(new InputStreamReader(serveur.getInputStream()));
         BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
         String jEnvoie, jeReçois;
         do  {
            jEnvoie = in.readLine();
            Sout.println(jEnvoie); 
            Sout.flush();
            jeReçois = Sin.readLine();
            System.out.println(jeReçois);
         }
         while (! jEnvoie.equals("fin"));
         Sin.close();   
         Sout.close();
         serveur.close();
      }
   }

39.6.2 Le serveur TCP

 

   public class ServerSocket {
      public ServerSocket(int port) throws IOException
      public ServerSocket(int port, int backlog) throws IOException
      public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException
      public InetAddress getInetAddress()
      public int getLocalPort()
      public Socket accept() throws IOException
      protected final void implAccept(Socket s) throws IOException
      public void close() throws IOException
      public void setSoTimeout(int timeout) throws SocketException
      public int getSoTimeout() throws IOException
      public String toString()
      public static void setSocketFactory(SocketImplFactory fac) throws IOException
   }

Une application serveur doit créer un instance ServerSocket:

 
   try {
      ServerSocket sock = new ServerSocket(2000);
   }
   catch (IOException e) {
      System.out.println("Erreur de création du serveur sur le port 2000");
      System.exit(-1);
   }
Une fois le serveur crée, il va se mettre en attente d'une connexion cliente:
 
   Socket sockClient;
   try {
      sockClient= serverSocket.accept();
   }
   catch (IOException e) {
      System.out.println("Echec de la mise en attente sur le port 2000");
      System.exit(-1);
   }

La méthode accept permet au serveur d'être à l'écoute du port 2000 et d'attendre une future connexion cliente. Lorsqu'une telle connexion s'établit, la varibale sockClient permet de récupérer le canal de communication établit avec le client grâce aux méthodes getInputStream et getOutputStream.

Pour illustrer le fonctionnement du serveur, programmons le fameux serveur echo.

 


   import java.io.*;
   import java.net.*;

   public class TestEcho {
      public static void main(String[] args) throws Exception {
         ServerSocket serveur = new ServerSocket(2000);
         while (true) {
            Socket client = serveur.accept();
            PrintWriter Cout = new PrintWriter(client.getOutputStream());
            BufferedReader Cin = new BufferedReader(new InputStreamReader(client.getInputStream()));
            String jeReçois;
            do  {
               jeReçois = Cin.readLine();
               Cout.println("Vous avez dit " + jeReçois); 
               Cout.flush();
            }
            while (! jeReçois.equals("fin"));
            Cin.close();   
            Cout.close();
            client.close();
         }
      }
   }

39.6.3 Connexions multiples

Le serveur TestEcho est bien trop simpliciste. En effet, ce serveur n'accepte qu'un client à la fois. Que se passe-t-il si plusieurs clients essayent de se connecter en même temps ? Le premier qui se connecte monopolise la connexion et les autres clients doivent attendre la fin de communication du premier pour pouvoir dialoguer avec le serveur à tour de rôle.

Cette solution est très restrictive. Nous allons modifier le serveur pour qu'il puisse répondre à plusieurs clients à la fois. Pour ce faire, le serveur devra créer un thread par client connecté de manière à pouvoir se remettre en attente d'un éventuel futur client.

Le squelette d'un programme qui réalise ceci est de la forme:

 
while (true) {
   attente d'un connexion
   créer un thread pour dialoguer avec le client qui s'est connecté
}
 

   import java.io.*;
   import java.net.*;

   public class TestEchoMultiple {
      public static void main(String[] args) throws Exception {
         ServerSocket serveur = new ServerSocket(2000);
         while (true) {
            Socket client = serveur.accept(); System.out.println("C'est par");
            new EncoreUnClient(client).start();
         }
      }
   }

   class EncoreUnClient extends Thread {
      Socket client;
      public EncoreUnClient(Socket client) {
         super("Clients");
         this.client = client;
      }
   
      public void run()  {
         PrintWriter Cout = null;
         BufferedReader Cin = null;
         try {
         System.out.println("C'est parti");
            Cout = new PrintWriter(client.getOutputStream());
            Cin = new BufferedReader(new InputStreamReader(client.getInputStream()));
            String jeReçois;
            do  {
               jeReçois = Cin.readLine();
               Cout.println("Vous avez dit " + jeReçois); 
               Cout.flush();
            }
            while (! jeReçois.equals("fin"));
            
            if (Cin != null) Cin.close();   
            if (Cout != null) Cout.close();
            client.close();
         }
            catch (IOException e) { e.printStackTrace(); }
      }
   }

39.7 Datgrammes UDP et sockets

On dispose de deux classes pour la communication par UDP:

Ainsi, l'envoi de données par UDP consite à les confier à un DatagramPacket en vue de leur emballage sous forme de paquets et à envoyer ce DatagramPacket à un DatagramSocket pour leur expédition. Un DatagramSocket se charge de la récéption; les paquets reçus doivent être mis dans un DatagramPacket en vue de leur déballage.

39.8 La classe DatagramPacket

 

   public final class DatagramPacket {
      public DatagramPacket(byte ibuf[], int ilength)
      public DatagramPacket(byte ibuf[], int ilength, InetAddress iaddr, int iport)
      public synchronized InetAddress getAddress()
      public synchronized int getPort()
      public synchronized byte[] getData()
      public synchronized int getLength()
      public synchronized void setAddress(InetAddress iaddr)
      public synchronized void setPort(int iport)
      public synchronized void setData(byte ibuf[])
      public synchronized void setLength(int ilength)
   }

La classe DatagramPacket dispose de deux constructeurs

 
public DatagramPacket(byte ibuf[], int ilength)
public DatagramPacket(byte ibuf[], int ilength, InetAddress iaddr, int iport)
Le premier constructeur est utiliser pour recevoir les paquets et le deuxième pour en envoyer.

Les méthodes getAddress, getPort, getLength et getData permettent de récupérer respectivement l'adresse de destination, le port de destination, la taille du paquet et les données contenues dans le paquet.

Les méthodes setAddress, setPort, setLength et setData assigne respectivement l'adresse de destination, le port de destination, la taille du paquet et les données contenues dans le paquet.

39.9 La classe DatagramSocket

 

   Class java.net.DatagramSocket {
      public DatagramSocket() throws SocketException
      public DatagramSocket(int port) throws SocketException
      public DatagramSocket(int port, InetAddress laddr) throws SocketException
      public void send(DatagramPacket p) throws IOException
      public synchronized void receive(DatagramPacket p) throws IOException
      public InetAddress getLocalAddress()
      public int getLocalPort()
      public synchronized void setSoTimeout(int timeout) throws SocketException
      public synchronized int getSoTimeout() throws SocketException
      public void close()
   }

La classe DatagramSocket possède trois constructeurs:

Les méthodes send et receive de cette classe permettent d'envoyer et de recevoir les packets UDP.

La méthode send lève une exception lorsque le SecurityManager refuse l'envoi d'un paquet vers l'hôte spécifié. C'est le cas ou une erreur peut se produire.

La méthode receive (tout comme la méthode accept de la classe SocketServer) se met en attente et lève également une exception lorsqu'il y une erreur dans la reception du paquet UDP. La méthode receive doit disposer d'un tampon assez grand pour le packet à recevoir. Dans le cas contraire, le paquet est tronqué à la taille du tampon.

39.10 Le client UDP

Une application cliente doit ouvrir une connexion sur en créant un socket.
 
   try {
      socket sock = new Socket("gbm.esil.univ-mrs.fr", 2000);
   }
   catch (UnknoxnHostException e) { System.out.println("Hôte inconnu"); System.exit(-1); }
   catch (IOException e) { System.out.println("Erreur lors de connexion"); System.exit(-1);}
La connexion établie, les canaux d'entrée/sortie s'obtiennent grâce aux méthode getInputStream et getOutputStream.

Pour tester le client, mous allons utiliser une application serveur qui existe sur tous les systèmes UNIX et qui est à l'écoute du port 7: il s'agit du serveur echo qui se contente de renvoyer au client la chaîne que ce dernier lui envoie.

 


   import java.io.*;
   import java.net.*;
   public class Client {
      public static void main(String[] args) throws Exception, NumberFormatException {
         Socket serveur = new Socket("gbm.esil.univ-mrs.fr", Integer.parseInt(args[0]));
         PrintWriter Sout = new PrintWriter(serveur.getOutputStream());
         BufferedReader Sin = new BufferedReader(new InputStreamReader(serveur.getInputStream()));
         BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
         String jEnvoie, jeReçois;
         do  {
            jEnvoie = in.readLine();
            Sout.println(jEnvoie); 
            Sout.flush();
            jeReçois = Sin.readLine();
            System.out.println(jeReçois);
         }
         while (! jEnvoie.equals("fin"));
         Sin.close();   
         Sout.close();
         serveur.close();
      }
   }

39.11 Le serveur UDP

Et voici à présent un serveur UDP à l'image du serveur TCP que nous vu. Il faut donc :

Et voici le programme complet.

 


   import java.io.*;
   import java.net.*;

   public class UdpTestEcho {
      public static void main(String[] args) throws Exception {
      
         String jeReçois;
         DatagramSocket socket = new DatagramSocket(2001);
         while (true) {
         
            byte[] buf = new byte[56];
            DatagramPacket paquet  = new DatagramPacket(buf, buf.length);
            socket.receive(paquet);
            jeReçois = new String(paquet.getData(), 0, paquet.getLength());
            System.out.println("j'ai recu : " + jeReçois + " " + jeReçois.length());
         
            InetAddress address = paquet.getAddress();
            int port = paquet.getPort();
            paquet = new DatagramPacket(buf, buf.length, address, port);
            socket.send(paquet);
         }
      }
   }

39.12 URLConnection

 La classe URLConection est une une classe abstraite qui permet une interaction de plus haut niveau que l'utilisation brute de la classe URL. En fait, cette classe permet de manipuler le contenu d'une URL bien plus simplement: entete MIME, téléchargement, requete CGI, etc.

On entend par gestion de protocole la prise en charge du protocole, le traitement des données spécifiques et leur présentation à l'utilisateur. La classe URLConnection sert également à cette gestion; ce que nous verrons dans la section suivante.

 
   public abstract class URLConnection  {
      protected URL url
      protected boolean doInput
      protected boolean doOutput
      protected boolean allowUserInteraction
      protected boolean useCaches
      protected long ifModifiedSince
      protected boolean connected
     
      protected URLConnection(URL url)
     
      public static FileNameMap getFileNameMap()
      public static void setFileNameMap(FileNameMap map)
      public abstract void connect() throws IOException
      public URL getURL()
      public int getContentLength()
      public String getContentType()
      public String getContentEncoding()
      public long getExpiration()
      public long getDate()
      public long getLastModified()
      public String getHeaderField(String name)
      public int getHeaderFieldInt(String name, int Default)
      public long getHeaderFieldDate(String name, long Default)
      public String getHeaderFieldKey(int n)
      public String getHeaderField(int n)
      public Object getContent() throws IOException
      public InputStream getInputStream() throws IOException
      public OutputStream getOutputStream() throws IOException
      public String toString()
      public void setDoInput(boolean doinput)
      public boolean getDoInput()
      public void setDoOutput(boolean dooutput)
      public boolean getDoOutput()
      public void setAllowUserInteraction(boolean allowuserinteraction)
      public boolean getAllowUserInteraction()
      public static void setDefaultAllowUserInteraction(boolean defaultallowuserinteraction)
      public static boolean getDefaultAllowUserInteraction()
      public void setUseCaches(boolean usecaches)
      public boolean getUseCaches()
      public void setIfModifiedSince(long ifmodifiedsince)
      public long getIfModifiedSince()
      public boolean getDefaultUseCaches()
      public void setDefaultUseCaches(boolean defaultusecaches)
      public void setRequestProperty(String key, String value)
      public String getRequestProperty(String key)
      public static void setDefaultRequestProperty(String key, String value)
      public static String getDefaultRequestProperty(String key)
      public static synchronized void setContentHandlerFactory(ContentHandlerFactory fac)
      protected static String guessContentTypeFromName(String fname)
      public static String guessContentTypeFromStream(InputStream is) throws IOException
   }

On obtient généralement une instance de la classe URLConection en retour de l'invocation de la méthode openConnection de la classe URL.

 
...
URL u = new URL("http://gbm.esil.univ-mrs.fr/index.html");
URLConnection uc = u.openConnection();
...
La seule méthode abstraite de cette classe est la méthode connect; toutes les autres méthodes sont implantées.

Les méthodes principales de cette classe sont connect, getContent, getInputStream et getOutputStream.

La méthode abstraite connect permet d'ouvrir une connection vers un url. Cette méthode est redéfinie par les sous classes de la classe URLConnection; c'est le cas de la classe HttpURLConnection. La méthode connect est rarement utilisé, c'est la méthode openConnection qui invoque cette méthode pour la bonne version de protocole.

La méthode getContent est celle invoquée par la méthode de même nom de la classe URL: elle retoune le contenu d'une url. L'exception UnknownServiceException est levée si le protocole et/ou le type MIME de l'url n'est pas reconnu.

La méthode getInputStream peut être utilisé lorsque getContent ne peut l'être. L'utilisation de cette méthode est similaire à la méthode openStream de la classe URL.

 
...
URL = u = new URL(...);
URLConnection uc = u.openConnection();
BufferedReader in = new BufferedReader(new InputStreamReader(url.openInputStream()));
...
String s = in.readLine();
...

La méthode getOutputStream permet d'envoyer des données à une url (méthode POST des CGI par exemple). Avant d'utiliser cette méthode, il faut invoquer la méthode setDoOutput car, par défaut, les URLConnection ne permettent pas d'envoyer des données.

 
...
URL = u = new URL("http://gbm.esil.univ-mrs/cgi_bin/programme);
URLConnection uc = u.openConnection();
uc.setDoOutput(true);
PrintWriter out = new PrintWriter(url.openOutputStream());
out.writeln(...);
...

Les méthodes getContentType, getContentLength,getContentEncoding permettent de consulter le type MIME, la longueur et le type d'encodage.

Les méthodes getHeaderField, getHeaderFieldKey, getHeaderFieldKeyDate et getHeaderFieldKeyInt permettent de récupérer les informations insérées dans les entêtes MIME.

 

   import java.io.*;
   import java.net.*;
   import java.util.*;

   public class TestContent {
      public static void main(String[] args) {
         BufferedReader in  = null;
         try {
            if (args.length != 1) {
               System.out.println("usage: java OpenURL <URL>");
               System.exit(0);
            }
            URL url = new URL(args[0]);
            URLConnection uc = url.openConnection();
            System.out.println(uc.getContentType());
            System.out.println(uc.getContentLength());
            System.out.println(uc.getContentEncoding());
         
            System.out.println(new Date(uc.getDate()));
            System.out.println(new Date(uc.getExpiration()));
            System.out.println(new Date(uc.getLastModified()));
         
            int i=0;
            String nom=null;
            do {
               System.out.println(uc.getHeaderFieldKey(i) + ": " + (nom=uc.getHeaderField(i++)));
            }
            while (nom != null);
         
            System.out.println(uc.getHeaderField("content-type"));
            System.out.println(uc.getHeaderField("content-length"));
            System.out.println(uc.getHeaderField("date"));            
         }
            catch (IOException e) {
               System.out.println("Erreur1");}
            catch (Exception e) {
               System.out.println("Erreur2");}
            finally {
               try { 
                  if (in != null) in.close();  }
                  catch (Exception e) {
                     System.out.println("Erreur3");}
            }
      }
   }

A priori, si toutes les conventions de nommage sont respectées, le type MIME peut se déduire aisément de l'extension du nom du la ressource url spécifié.

Extension Type MIME .pdf

En exécutant l'exemple précédant, on obtient bien le type des fihciers spécifiés:

 
% java TestContent http://gbm.esil.univ-mrs.fr/~tourai/images/photo.jpg 
Content-type: text/html
Par contre si l'extension n'est pas correct, on alors des surprises; le fichier photo est une copie renommée du fichier photo.jpg
 
% java TestContent http://gbm.esil.univ-mrs.fr/~tourai/images/photo 
Content-type: text/plain

Aussi, Java dispose de deux méthodes pour deviner le type MIME associé à un fichier:

39.13 Un petit serveur HTTP

39.14 Un CGI en Java

 

A TERMINER

39.15 Gestion de protocoles

 

A TERMINER

39.16 ContentHandler

 

A TERMINER

39.17 Gestion de protocoles

 

A TERMINER

39.18 Sockets multipoints

 

A TERMINER


next up previous contents index
Next: 40 Appel de méthodes Up: Java: Programmation avancée Previous: Java: Programmation avancée
Touraivane
6/12/1998