Clients TCP non bloquants

Principe général

Les concepts de TCP non-bloquant étudiés pour les serveurs s'appliquent :

  • sélecteur,
  • boucle de sélection,
  • contextes,
  • ...

Il y a 2 nouveautés :

  • La gestion de la connexion en mode non-bloquant, avec la méthode socketChannel.connect() ;
  • Les interactions entre la boucle de sélection et d'autres threads (thread effectuant les lectures au claviers ou threads de l'UI).

Etablissement de la connexion

En mode bloquant, socketChannel.connect bloque jusqu'à l'établissement de la connexion.

En mode non-bloquant,

  • socketChannel.connect initie la connexion ;
  • si la socketChannel est enregistrée en SelectionKey.OP_CONNECT auprès d'un sélecteur, le sélecteur notifie quand la connexion est établie ;
  • boolean socketChannel.finishConnect permet de tester que la connexion a bien été établie.

Initiation de la connexion et enregistrement

var sc = SocketChannel.open();
sc.configureBlocking(false);
sc.connect(serverAddress);
var sKey = sc.register(selector, SelectionKey.OP_CONNECT);

L'appel à sc.connect(serverAddress) initie la connexion. Le sélecteur nous préviendra quand la connexion est établie.

Boucle de sélection

while (!Thread.interrupted()) {
   selector.select(this::treatKey);
}                

avec

void treatKey(SelectionKey key) {
    if (key.isValid() && key.isConnectable()) {
        doConnect(key);
    }
    if (key.isValid() && key.isWritable()) {
        doWrite(key);
    }
    if (key.isValid() && key.isReadable()) {
        doRead(key);
    }
}           

Attention aux exceptions...

doConnect

private void doConnect(SelectionKey key) throws IOException {
   if (!sc.finishConnect())
      return; // the selector gave a bad hint
   key.interestOps(SelectionKey.OP_READ);
}              

Interaction avec les autres threads

Dans un client non-bloquant, les trames à envoyer au serveur viennent en général d'un autre thread que celui exécutant la boucle de sélection :

  • thread lisant au clavier,
  • thread de l'interface graphique,
  • ...

Problème : les sélecteurs ne sont pas thread-safe !

On ne peut pas modifier les interestOps des clés, appeler register, ... dans un autre thread que celui qui exécute la boucle de sélection.

La seule méthode thread-safe est selector.wakeup() qui force le selector à sortir de selector.select.