Socket non bloquant

Les opérations sur les sockets TCP sont par défaut bloquantes. Dans le cas d'un socket serveur, cela signifie que le flux d'exécution est bloqué au niveau de l'appel à la méthode accept tant qu'un client ne s'est pas connecté.

socket = socServeur.accept(); // bloqué en attente d'une connexion

Dans le cas d'un socket ordinaire, cela veut dire que le flux d'exécution est interrompu tant que l'opération de lecture sur le socket n'est pas complétée.

texte = reader.readLine(); // bloqué en attente d'une ligne de texte

Cette situation de blocage par défaut peut compliquer l'écriture des programmes, ne serait-ce qu'à cause de l'impossibilité pour l'utilisateur de mettre fin proprement au programme pendant que le socket est bloqué. Bien sûr on pourrait utiliser les threads, mais une solution plus simple est à notre portée.

La méthode setSoTimeout

Les classes ServerSocket et Socket offrent la possibilité de fixer un délai d'attente (timeout en anglais). Ainsi, un serveur en attente d'une connexion peut “débloquer” après un certain temps si aucun client ne s'y connecte. De même, une application qui lit sur un socket (ordinaire) peut mettre fin à l'opération, même si la lecture n'est pas complétée, à partir du moment où le délai fixé est expiré.

La fixation du délai, autant pour un socket serveur que pour un socket ordinaire, se fait par l'appel à la méthode setSoTimeout. Cette méthode doit évidemment être appelée après la création du socket, mais avant l'appel à une méthode bloquante. Après expiration du délai, si aucune connexion n'a été faite au socket serveur ou que la lecture n'est pas complétée sur un socket ordinaire, une exception de type SocketTimeoutException est levée.

Voyons d'abord le cas d'un socket serveur :

try {
    socServeur = new ServerSocket(PORT);
    socServeur.setSoTimeout(DELAI); // délai en millisecondes
    soc = socServeur.accept(); // appel à une méthode bloquante
    System.out.println("Connexion!");
} catch (SocketTimeoutException ste) {
    System.err.println("Délai expiré");
} catch (IOException ioe) {
    System.err.println("Erreur de communication");
}

Le code que vous placerez dans le premier bloc catch dépend de l'application. Il peut être utilisé par exemple pour vérifier si l'utilisateur n'a pas entré une commande dans la console du serveur pour, par exemple, mettre fin au programme.

Attention à ne pas inverser l'ordre des deux blocs catch, car SocketTimeoutException est dérivée de IOException.

Voyons maintenant le cas d'un socket ordinaire :

// exemple de délai de connexion
try {
    InetSocketAddress adr = new InetSocketAddress(ADRESSE, PORT);
    Socket soc = new Socket();
    soc.setSoTimeout(DELAI);

    // la ligne suivante bloque l'exécution du programme jusqu'à la connexion
    // ou l'expiration du délai
    soc.connect(adr);
    
    System.out.println("Connexion!");
    // ...
} catch (SocketTimeoutException ste) {
    System.err.println("Délai de connexion expire");
} catch (IOException ioe) {
    System.err.println("Connexion impossible");
}
// exemple de délai de lecture
try {
    InetSocketAddress adr = new InetSocketAddress(ADRESSE, PORT);
    Socket soc = new Socket();
    soc.setSoTimeout(DELAI);
    soc.connect(adr);

    BufferedReader reader = new BufferedReader(
            new InputStreamReader(soc.getInputStream()));

    // la ligne suivante bloque l'exécution jusqu'à la lecture d'une ligne
    // ou l'expiration du délai
    String ligne = reader.readLine();
    
    // ...
} catch (SocketTimeoutException ste) {
    System.err.println("Délai de lecture expire");
} catch (IOException ioe) {
    System.err.println("Erreur d'entrée-sortie");
}