Java NIO select () retourne sans les touches sélectionnées - pourquoi?
Question
En écrivant du code de test, j'ai constaté que Selector.select () peut renvoyer sans Selector.selectedKeys () contenant les clés à traiter. Cela se produit dans une boucle serrée lorsque je crée un canal accepté () avec
SelectionKey.OP_READ | SelectionKey.OP_CONNECT
en tant qu'opérations d'intérêt.
En fonction de la documentation, select () devrait revenir lorsque:
1) Certains canaux peuvent être utilisés.
2) Vous appelez explicitement Selector.wakeup () - aucune clé n'est sélectionnée.
3) Vous explicitement Thread.interrupt () le thread faisant le select () - aucune clé n'est sélectionnée.
Si je n’obtiens aucune clé après select (), je dois être dans les cas (2) et (3). Cependant, mon code n’appelle pas wakeup () ni interruption () pour initialiser ces retours.
Avez-vous des idées sur la cause de ce comportement?
La solution
Réponse courte: supprimez OP_CONNECT
de la liste des opérations qui vous intéressent pour la connexion acceptée - une connexion acceptée est déjà connectée.
J'ai réussi à reproduire le problème, ce qui pourrait être exactement ce qui vous arrive:
import java.net.*;
import java.nio.channels.*;
public class MyNioServer {
public static void main(String[] params) throws Exception {
final ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(true);
serverChannel.socket().bind(new InetSocketAddress("localhost", 12345));
System.out.println("Listening for incoming connections");
final SocketChannel clientChannel = serverChannel.accept();
System.out.println("Accepted connection: " + clientChannel);
final Selector selector = Selector.open();
clientChannel.configureBlocking(false);
final SelectionKey clientKey = clientChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_CONNECT);
System.out.println("Selecting...");
System.out.println(selector.select());
System.out.println(selector.selectedKeys().size());
System.out.println(clientKey.readyOps());
}
}
Une fois que le serveur ci-dessus a reçu une connexion, le tout premier select ()
de la connexion se ferme sans blocage et aucune clé n'est prête. Je ne sais pas pourquoi Java se comporte de cette façon, mais il semble que beaucoup de gens soient piqués par ce comportement.
Le résultat est identique sur la machine virtuelle Java 1.5.0_06 de Sun sous Windows XP, ainsi que sur la machine virtuelle Java 1.5.0_05 et 1.4.2_04 sous Linux 2.6.
Autres conseils
La raison en est que OP_CONNECT
et OP_WRITE
sont la même chose sous le capot, de sorte que vous ne devriez jamais être enregistré pour les deux simultanément (idem OP_ACCEPT
et OP_READ
), et vous ne devriez jamais être enregistré pour OP_CONNECT
lorsque le canal est déjà connecté, comme c'est le cas dans ce cas, après avoir été accepté.
Et OP_WRITE
est presque toujours prêt, sauf lorsque le tampon d’envoi du socket dans le noyau est plein. Vous ne devez donc vous enregistrer pour cela qu'après avoir obtenu une écriture de longueur nulle. Donc, en enregistrant le canal déjà connecté pour OP_CONNECT,
, vous vous enregistriez réellement pour OP_WRITE,
qui était prêt, donc select ()
a été déclenché.