Rappels sur la programmation réseau en Java :
Quelques rappels sur l'utilisation des sockets avec java.net

Patrick.Itey@sophia.inria.fr
Mars 1999

Sockets

Les sockets permettent d'implanter des connections réseaux fiables pour qu'un ou plusieurs clients puissent envoyer des requêtes à un serveur distant.

Le serveur

Le serveur utilise la classe ServerSocket pour accepter des connexions de clients.

Quand un client se connecte à un port sur lequel un ServerSocket écoute, le ServerSocket crée une nouvelle instance de la classe Socket pour supporter les communications.

int port = ...;
ServerSocket server = new ServerSocket(port);
Socket connection = server.accept();

L'exemple précédent montre comment utiliser les classes Socket et ServerSocket ensemble, du côté serveur.

Cependant, il fait abstraction de la gestion des exceptions que vous aurez à supporter.

La méthode accept est dite bloquante.

En effet, le programme reste en attente jusqu'à ce qu'une demande de connexion soit prise en compte et traitée.

Java ne propose pas de primitive non bloquante, ce qui impose un type de programmation particulier, car il paraît incongru de devoir attendre qu'une connexion se fasse.

Typiquement, le code proposé se place dans une boucle infinie qui se termine seulement si une erreur grave se produit.

Le client

Réciproquement, un client doit pouvoir se connecter à un serveur.

Le client crée donc une instance de la classe Socket pour établir une connexion synchrone avec le serveur:

String host =...; 
int port = ...; 
Socket connection = new Socket(host,port);

Ces trois lignes de code permettent d'établir une connexion fiable avec le serveur.

Pour pouvoir fonctionner, le serveur doit être démarré auparavant.

Dans le cas contraire, le client tenterait de se connecter à un serveur inexistant ce qui, après un time-out se traduirait par la levée d'une exception.

Tout comme pour le serveur, le code présenté ne prend pas en compte la gestion des exceptions. Un code compilable devra intégrer cette gestion.

Flux de données

Une fois les connexions réalisées, il suffit d'obtenir les streams (flux) d'entrée et de sortie auprès de l'instance de la classe Socket en cours.

Flux entrants

InputStream in = socket.getInputStream(); 
InputStreamReader reader = new InputStreamReader(in); 
BufferedReader istream = new BufferedReader(reader); 
String line=istream.readlLine();

Ces lignes permettent de recevoir des données.

Chaque ligne de code s'appuie sur les flux de données (les streams) que l'on trouve dans le package java.io et représente une étape dans la prise en compte des flux de données :

  1. Obtention d'un stream simple qui définie toutes les opérations de bases supportées par les flux d'entrée.
  2. Création d'un stream convertissant les bytes reçus sur le réseau en caractères.
    Chaque lecture d'un caractères par l'utilisateur entraîne la lecture d'un ou plusieurs bytes sur le réseau de manière transparente.
  3. Création en dernier lieu d'un stream de lecture avec un tampon pour lire ligne à ligne dans un stream de caractères.
    L'utilisation directe d'un InputStreamReader serait inefficace et c'est pour cela qu'il faut utiliser un BufferedReader.
  4. Lecture d'un chaîne de caractères.

Flux sortant

OutputStream out = socket.getOutputStream();   
PrintWriter ostream = new PrintWriter(out); 
ostream.println(str); 
ostream.flush();

Ces quatre nouvelles lignes permettent d'établir un flot de données sortantes.

Comme précédemment, les streams du package java.io sont utilisées et une chaîne de caractères est envoyée, en quatre étapes:

  1. Obtention du flot des données sortantes. Ce flot représente des données sous formes de bytes.
  2. Création d'un stream pour pouvoir convertir des chaînes de caractères sous forme de bytes.
  3. Envoie d'une ligne de caractères.
  4. Envoie effectif sur le réseau des bytes. Cette ligne est importante car elle permet l'envoi effectif. Si l'envoi était fait à chaque fois que l'on invoque la méthode println, les communications seraient totalement inefficaces.