Programmation Réseau en Java
Support Numéro 1
Sockets clients
Introduction
Le langage Java a été conçu par SUN, une société donc l’activité est fortement tournée vers le réseau et qui a mis au point plusieurs protocoles réseaux utilisés sur Internet (RPC, NFS, NIS). Il peut donc paraître logique que ce langage soit tourné vers le réseau et notamment vers Internet.
Courte bibliographie :
Ces supports sont basés sur les sources suivantes :
Rappels
UNIX est un système multi-tâches et multi-utilisateurs. Les processus peuvent coopérer entre eux. Différents outils de communication inter-processus ont été développés: les IPC (Inter Process Communication).
La communication interprocess peut se faire à travers les signaux et les pipes. Mais tandis que la version d'ATT était orientée gestion de ressources (sémaphores, messages, mémoire partagée), l'université de Berkeley a développé une version orientée réseaux en implémentant les protocoles Internet ARPA/DOD et en offrant l'interface des "Sockets".
Les Sockets forment une API (Application Program Interface): ils offrent aux programmeurs une interface entre le programme d'application et les protocoles de communication. En aucun cas, les sockets ne forment une norme de communication ou une couche de protocole à l'instar de TCP/IP.
Les sockets (prises de raccordement) forment un mécanisme de communication bidirectionnel interprocessus dans un environnement distribué ce qui n'est pas le cas des autres outils tels que les pipes. Ils permettent évidemment la communication interprocess à l'intérieur d'un même système.
L'interface des "sockets" n'est pas liée à une pile de protocoles spécifique. Dans ce cours, nous nous intéresserons, à l'utilisation des sockets dans le monde TCP/IP.
La notion de socket en tant que prise de raccordement vient d’une analogie avec le réseau électrique et le réseau téléphonique :
Figure 1 : La métaphore des prises
Les sockets représentent donc d'une part une API c'est à dire un ensemble de primitives de programmation et d'autre part les extrémités de la communication (notion de prise). La mise en oeuvre des sockets nécessite donc la connaissance des primitives ainsi que le modèle client/serveur utilisé.
La classe SocketLa classe Socket représente en Java uniquement les sockets utilisant le protocole TCP (garantissant la fiabilité du service) et fonctionnant en mode client (pas d’attente de connexion via un accept). Nous verrons dans les TPs suivants : les ServerSocket qui permettent l’attente de connexion et qui utilisent eux aussi TCP et les DatagramSocket qui permettent une communication utilisant le protocole UDP (sans garantie de fiabilité).
1. Constructeurs
public Socket (String hote, int port) throws UnknownHostException, IOException
Ce constructeur crée un socket TCP sur le port indiqué de l’hôte visé et tente de s’y connecter. Le premier paramètre de ce constructeur représente le nom de la machine serveur. Si l’hôte est inconnu ou que le serveur de noms de domaine est inopérant, le constructeur générera une UnknownHostException. Les autres causes d’échec, qui déclenchent l’envoi d’une IOException sont multiples : machine cible refusant la connexion sur le port précisé ou sur tous les ports, problème lié à la connexion Internet, erreur de routage des paquets…
Voici un exemple d’utilisation de ce constructeur :
Socket leSocket = new Socket("www.irit.fr", 80);
public Socket (InetAddress addresse, int port) throws IOException
Ce constructeur fonctionne comme le premier, mais prends comme premier paramètre une instance de la classe InetAddress. Ce constructeur provoque une IOException lorsque la tentative de connexion échoue mais il ne renvoie pas d’UnknownHostException puisque cette information est connue lors de la création de l’objet InetAddress.
public Socket(String hote, int port, InetAddress addresseLocale, int portLocal) throws IOException
Comme les deux précédents, ce constructeur crée un socket sur le port spécifié de l’hôte visé et tente de s’y connecter. Le numéro du port et le nom de la machine sont fournis dans les deux premiers paramètres et la connexion s’établit à partir de l’interface réseau physique (choix d’une carte réseau sur un système possédant plusieurs accès réseau) ou virtuelle (système multi-adresse) et du port local indiqués dans les deux derniers paramètres. Si portLocal vaut 0, la méthode (comme les deux premières d’ailleurs) choisit un port disponible (parfois appelé port anonyme) compris entre 1024 et 65 535.
Comme la première méthode cette méthode peut générer une IOException en cas de problème de connexion et lorsque le nom d’hôte donné n’est pas correct (il s’agit dans ce cas d’une UnknownHostException).
public Socket(InetAddress address, int port, InetAddress addresseLocale, int portLocal)
throws IOException
Cette méthode est identique à la précédente à la seule différence que le premier paramètre est, comme pour la seconde méthode, un objet InetAddress.
protected Socket()
protected Socket(SocketImpl impl) throws SocketException
Ces deux derniers constructeurs sont protégés. Ils créent un socket sans le connecter. On utilise ces constructeurs pour implanter un socket original qui permettra par exemple de chiffrer les transactions ou d’interagir avec un Proxy.
Il existe de plus deux autres constructeurs qui permettent le choix du protocole via un booléen mais ces méthodes sont dépréciées et ne doivent pas être utilisées. Pour créer un socket utilisant UDP on utilisera DatagramSocket.
2. Méthodes informatives
public InetAddress getInetAddress ()
public int getPort ()
Ces méthodes renvoient l'adresse Internet et le port distants auquel le socket est connecté.
public InetAddress getLocalAddress ()
public int getLocalPort ()
Ces méthodes renvoient l'adresse Internet et le port locaux sur lequel le socket est connecté.
Communication avec un socket
public InputStream getInputStream () throws IOException
Cette méthode renvoie un flux d’entrées brutes grâce auquel un programme peut lire des informations à partir d’un socket. Il est d’usage de lier cet InputStream à un autre flux offrant d’avantage de fonctionnalités (un DataInputStream par exemple) avant d’acquérir les entrées.
Voici un exemple d’utilisation de cette méthode :
DataInputStream fluxEnEntree = new DataInputStream(leSocket.getInputStream());
public OutputStream getOutputStream () throws IOException
Cette méthode renvoie un flux de sortie brutes grâce auquel un programme peut écrire des informations sur un socket. Il est d’usage de lier cet OutputStream à un autre flux offrant d’avantage de fonctionnalités (un DataOutputStream par exemple) avant d’émettre des données.
Voici un exemple d’utilisation de cette méthode :
DataOutputStream fluxEnSortie = new DataOutputStream(leSocket.getOutputStream());
3. Fermeture d’un socket
public void close() throws IOException
Bien que Java ferme tous les sockets ouverts lorsqu’un programme se termine ou bien lors d’un " garbage collect ", il est fortement conseillé de fermer explicitement les sockets dont on n’a plus besoin à l’aide de la méthode close.
Une fois un socket fermé on peut toujours utiliser les méthodes informatives, par contre toute tentative de lecture ou écriture sur les " input/output streams " provoque une IOException.
4. Options des sockets
Java permet l’accès à un certain nombre d’options qui modifient le comportement par défaut des sockets. Ces options correspondent à celles que l’on manipule en C via ioctl (ou ioctlsocket avec Windows).
public boolean getTcpNoDelay() throws SocketException
public void setTcpNoDelay(boolean valide) throws SocketException
Valide/invalide l’option TCP_NODELAY. Valider TCP_NODELAY garantit que les paquets seront expédiés aussi rapidement que possible quelle que soit leur taille. Ceci correspond à interdire la mise en œuvre de l’algorithme de Nagle qui, en simplifiant, concatène les paquets de taille réduite dans des paquets de taille supérieure avant envoi.
Si l’on programme une application très interactive on pourra valider l’option TCP_NODELAY.
public int getSoLinger() throws SocketException
public void setSoLinger(boolean valide, int secondes) throws SocketException
L’option SO_LINGER indique le comportement à adopter lorsqu’il reste des paquets à expédier au moment de la fermeture du socket. Par défaut, la méthode close() rend la main immédiatement et le système tente d’envoyer le reliquat de données. Si la durée des prolongations secondes vaut 0 et que valide est à vrai, les éventuels paquets restant sont supprimés lorsque la fermeture à lieu. Si la durée est positive et que valide est à vrai, la méthode close se fige pendant le nombre de secondes spécifiées en attendant l’expédition des paquets et la réception de l’acquittement. Une fois les prolongations écoulées, le socket est clos et les données restantes ne sont pas transmises.
Si cette option n’est pas validée (valide est à faux) on obtient le comportement par défaut et l’appel de la méthode getSoLinger() retourne –1, sinon la méthode renvoie la durée des prolongations.
public int getSoTimeout() throws SocketException
public void setSoTimeout(int ms) throws SocketException
Par défaut, lors d’une tentative de lecture sur le flux d’entrée d’un socket, read() se bloque jusqu’à la réception d’un nombre d’octets suffisant. Lorsque SO_TIMEOUT est initialisée, cette attente ne dépasse pas le temps imparti, exprimé en millisecondes. Tout dépassement de temps se solde par une InterruptedException. Le socket reste malgré tout connecté.
La valeur par défaut est 0 qui signifie, ici, un laps de temps infini. Il peut être préférable de choisir une durée raisonnable (>0) pour éviter que le programme se bloque en cas de problème.
L’option doit être validée avant l’appel à l’opération bloquante pour être prise en compte.
Le paramètre doit être >= 0 sans quoi une exception SocketException est générée.
public int getSendBufferSize() throws SocketException
public void setSendBufferSize(int size) throws SocketException
public int getReceiveBufferSize() throws SocketException
public void setReceiveBufferSize(int size) throws SocketException
Ces méthodes permettent, grâce aux options SO_SNDBUF et SO_RCVBUF, de préciser une indication sur la taille à allouer aux tampons d’entrée/sortie bas niveaux. Il s’agit uniquement d’une indication donc il est conseillé après l’appel d’une méthode set… d’appeler la méthode get… correspondante pour vérifier la taille allouée.
5. Méthode surchargée
public String toString()
La seule méthode surchargée de la classe Object est toString(). Cette méthode provoque l’affichage d’une chaîne ressemblant à cela :
Socket[addr=amber/192.11.4.2,port=80,localport=1167]
Cette méthode est donc plutôt destinée au débogage.