Réimpression : Réseau de programmation simultanée : Tutoriel ifeve.com NIO
Selector (sélecteur) est un NIO Java qui peut détecter un ou plusieurs canaux NIO et savoir si le canal est prêt pour les événements de lecture et d'écriture préparés. composants. De cette manière, un seul thread peut gérer plusieurs canaux et donc plusieurs connexions réseau.
L'avantage d'utiliser un seul thread pour traiter plusieurs canaux est que moins de threads sont nécessaires pour traiter les canaux. En fait, il est possible de n’utiliser qu’un seul thread pour gérer tous les canaux. Pour le système d'exploitation, le changement de contexte entre les threads est très coûteux et chaque thread occupe certaines ressources système (telles que la mémoire). Par conséquent, moins il y a de fils utilisés, mieux c'est.
Cependant, il est important de se rappeler que les systèmes d'exploitation et les processeurs modernes s'améliorent de plus en plus en multitâche, de sorte que la surcharge du multithreading devient de plus en plus petite avec le temps. En fait, si un processeur possède plusieurs cœurs, ne pas utiliser le multitâche peut constituer un gaspillage de puissance du processeur. Quoi qu’il en soit, la discussion sur cette conception devrait faire l’objet d’un autre article. Ici, il suffit de savoir que vous pouvez gérer plusieurs canaux à l'aide du Sélecteur.
Créez un sélecteur en appelant la méthode Selector.open(), comme suit :
Selector selector = Selector.open();
Pour utiliser Channel et Selector ensemble, la chaîne doit être enregistrée auprès du sélecteur. Ceci est réalisé grâce à la méthode SelectableChannel.register(), comme suit :
channel.configureBlocking(false); SelectionKey key = channel.register(selector, Selectionkey.OP_READ);
Lorsqu'il est utilisé avec Selector, Channel doit être en mode non bloquant. Cela signifie que vous ne pouvez pas utiliser FileChannel avec un sélecteur car FileChannel ne peut pas être basculé en mode non bloquant. Les canaux de prise fonctionnent bien.
Faites attention au deuxième paramètre de la méthode register(). Il s'agit d'une « collection d'intérêts », c'est-à-dire les événements qui vous intéressent lorsque vous écoutez la chaîne via le sélecteur. Quatre types d'événements différents peuvent être écoutés :
Connecter
Accepter
Lire
Ecrire
La chaîne déclenche un événement, ce qui signifie que l'événement est prêt. Par conséquent, un canal qui se connecte avec succès à un autre serveur est appelé « prêt à se connecter ». Un canal socket serveur est dit « prêt à recevoir » lorsqu’il est prêt à recevoir des connexions entrantes. Un canal qui a des données à lire est dit « prêt à lire ». Un canal en attente d'écriture de données peut être dit « prêt à l'écriture ».
Ces quatre événements sont représentés par les quatre constantes de SelectionKey :
SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT
SelectionKey.OP_READ
SelectionKey.OP_WRITE
Si vous êtes intéressé par plus d'un événement, alors vous pouvez utiliser l'opérateur "OU au niveau du bit" pour connecter les constantes, comme suit :
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
La collection d'intérêts sera mentionnée ci-dessous.
Dans la section précédente, lors de l'enregistrement d'un canal avec le sélecteur, la méthode register() renverra un objet SelectionKey. Cet objet contient quelques propriétés qui vous intéressent :
collection d'intérêt
collection prête
Canal
Sélecteur
Objet supplémentaire (facultatif)
Je vais le décrire ci-dessous ces propriétés.
Comme décrit dans la section Enregistrement de chaînes avec un sélecteur, la collection d'intérêts est une collection d'événements intéressants que vous sélectionnez. Vous pouvez lire et écrire la collection d'intérêts via SelectionKey, comme ceci :
int interestSet = selectionKey.interestOps(); boolean isInterestedInAccept = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
Comme vous pouvez le voir, utiliser "bit AND" pour faire fonctionner la collection d'intérêts et la constante SelectionKey donnée peut déterminer si un certain événement est dans la collection d'intérêts.
la collection prête est un ensemble d'opérations pour lesquelles le canal est prêt. Après une sélection (Sélection), vous accéderez d'abord à l'ensemble prêt. La sélection sera expliquée dans la section suivante. Vous pouvez accéder à la collection prête comme ceci :
int readySet = selectionKey.readyOps();
Vous pouvez utiliser la même méthode que la collection d'intérêts pour détecter quels événements ou opérations dans le canal sont prêts. Cependant, les quatre méthodes suivantes sont également disponibles, qui renvoient toutes un type booléen :
selectionKey.isAcceptable(); selectionKey.isConnectable(); selectionKey.isReadable(); selectionKey.isWritable();
L'accès au canal et au sélecteur à partir de SelectionKey est simple. Comme suit :
Channel channel = selectionKey.channel(); Selector selector = selectionKey.selector();
peuvent attacher un objet ou plus d'informations à la SelectionKey, afin qu'un canal donné puisse être facilement identifié. Par exemple, vous pouvez attacher un Buffer à utiliser avec un canal ou un objet contenant des données agrégées. L'utilisation est la suivante :
selectionKey.attach(theObject); Object attachedObj = selectionKey.attachment();
Vous pouvez également attacher des objets lors de l'enregistrement du canal auprès du sélecteur à l'aide de la méthode register(). Par exemple :
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
Une fois qu'une ou plusieurs chaînes sont enregistrées avec Selector, vous pouvez appeler plusieurs méthodes select() surchargées. Ces méthodes renvoient les canaux prêts pour l'événement qui vous intéresse (comme se connecter, accepter, lire ou écrire). En d'autres termes, si vous êtes intéressé par les canaux « prêts à lire », la méthode select() renverra les canaux pour lesquels les événements de lecture sont prêts.
Voici la méthode select() :
int select()
int select(long timeout)
int selectNow()
select()
阻塞到至少有一个通道在你注册的事件上就绪了。
select(long timeout)
和select()一样,除了最长会阻塞timeout毫秒(参数)。
selectNow()
不会阻塞,不管什么通道就绪都立刻返回(译者注:此方法执行非阻塞的选择操作。如果自从前一次选择操作后,没有通道变成可选择的,则此方法直接返回零。)。
select()方法返回的int值表示有多少通道已经就绪。亦即,自上次调用select()方法后有多少通道变成就绪状态。如果调用select()方法,因为有一个通道变成就绪状态,返回了1,若再次调用select()方法,如果另一个通道就绪了,它会再次返回1。如果对第一个就绪的channel没有做任何操作,现在就有两个就绪的通道,但在每次select()方法调用之间,只有一个通道就绪了。
一旦调用了select()方法,并且返回值表明有一个或更多个通道就绪了,然后可以通过调用selector的selectedKeys()方法,访问“已选择键集(selected key set)”中的就绪通道。如下所示:
Set selectedKeys = selector.selectedKeys();
当像Selector注册Channel时,Channel.register()方法会返回一个SelectionKey 对象。这个对象代表了注册到该Selector的通道。可以通过SelectionKey的selectedKeySet()方法访问这些对象。
/** * selector */@Testpublic void test3() throws IOException { Selector selector = Selector.open(); SocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false);//向selector注册此通道SelectionKey register = socketChannel.register(selector, SelectionKey.OP_READ);//在本例,i == SelectionKey.OP_READint i = register.interestOps();//所以判断 可以通过这样来判断事件boolean b = (i & SelectionKey.OP_READ) == SelectionKey.OP_READ;//当然也可以通过 isXX方法来判断boolean readable = register.isReadable();//返回已经准备好的 SelectionKey数量,如果>0,表示有了,就可以调用下面的方法了int select = selector.select();/** * if select > 0,一般是 while(true)循环 *///这里面保存着已经 准备好的 SelectionKey,也就是通道//注意 这里面的 SelectionKey需要手动移除,不会自动移除Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator keyIterator = selectionKeys.iterator();while(keyIterator.hasNext()) { SelectionKey key = (SelectionKey) keyIterator.next();//获取通道SelectableChannel channel = key.channel();//获取selectorSelector selector1 = key.selector();if(key.isAcceptable()) {// a connection was accepted by a ServerSocketChannel.} else if (key.isConnectable()) {// a connection was established with a remote server.} else if (key.isReadable()) {// a channel is ready for reading} else if (key.isWritable()) {// a channel is ready for writing }//移除 keyIterator.remove(); }//关闭 selector.close(); }
注意每次迭代末尾的keyIterator.remove()调用。Selector不会自己从已选择键集中移除SelectionKey实例。必须在处理完通道时自己移除。下次该通道变成就绪时,Selector会再次将其放入已选择键集中。
SelectionKey.channel()方法返回的通道需要转型成你要处理的类型,如ServerSocketChannel或SocketChannel等。
某个线程调用select()方法后阻塞了,即使没有通道已经就绪,也有办法让其从select()方法返回。只要让其它线程在第一个线程调用select()方法的那个对象上调用Selector.wakeup()方法即可。阻塞在select()方法上的线程会立马返回。
如果有其它线程调用了wakeup()方法,但当前没有线程阻塞在select()方法上,下个调用select()方法的线程会立即“醒来(wake up)”。
用完Selector后调用其close()方法会关闭该Selector,且使注册到该Selector上的所有SelectionKey实例无效。通道本身并不会关闭。
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!