一、什么是selector
selector 一般称为选择器 ,当然你也可以翻译为 多路复用器 。它是Java NIO核心组件中的一个,用于检查一个或多个NIO Channel(通道)的状态是否处于可读、可写。如此可以实现单线程管理多个channels,也就是可以管理多个网络链接。
二、使用selector
1. Selector的创建
通过调用Selector.open()方法创建一个Selector对象,如下:
Selector selector = Selector.open();
2. 注册Channel到Selector,Channel必须是非阻塞的。
ssc.configureBlocking(false); //设置为非阻塞 SelectionKey key = channel.register(selector, Selectionkey.OP_READ)
(1)要先创建channel :
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().bing(new InetSocketAddress("localhost",9999))
//ssc.socket() : 检索与此通道关联的服务器套接字,返回一个ServerSocket
注意:abstract SelectableChannel configureBlocking(boolean block) 可以设置是否有阻塞
SelectableChannel抽象类的configureBlocking() 方法是由 AbstractSelectableChannel抽象类实现的,SocketChannel、ServerSocketChannel、 DatagramChannel都是直接继承了 AbstractSelectableChannel抽象类 。
(2)channel.register( )第二个参数:
register() 方法的第二个参数。这是一个“ interest集合 ”,意思是在通过Selector监听Channel时对什么事件感兴趣。可以监听四种不同类型的事件:
- Connect :connect==1<<3==8==0000 1000
- Accept :accept==1<<4==16==0001 0000
- Read : read ==1<<0 ==1 ==0000 0001
- Write :write==1<<2==4==0000 0100
通道触发了一个事件意思是该事件已经就绪。
1)比如某个Channel成功连接到另一个服务器称为“ 连接就绪 ”。
2)一个Server Socket Channel准备好接收新进入的连接称为“ 接收就绪 ”。
3)一个有数据可读的通道可以说是“ 读就绪 ”。
4)等待写数据的通道可以说是“ 写就绪 ”。
这四种事件用SelectionKey的四个常量来表示:
SelectionKey.OP_CONNECT //连接就绪 SelectionKey.OP_ACCEPT //接收就绪 SelectionKey.OP_READ //读就绪 SelectionKey.OP_WRITE //写就绪
(3) 一个SelectionKey键表示了一个特定的通道对象和一个特定的选择器对象之间的注册关系。
key.attachment(); //返回SelectionKey的attachment,attachment可以在注册channel的时候指定。
key.channel(); // 返回该SelectionKey对应的channel。
key.selector(); // 返回该SelectionKey对应的Selector。
key.interestOps(); //返回代表需要Selector监控的IO操作的bit mask
key.readyOps(); // 返回一个bit mask,代表在相应channel上可以进行的IO操作。
1)key.interestOps(); 可以通过以下方法来判断Selector是否对Channel的某种事件感兴趣
int interestSet = selectionKey.interestOps(); boolean isInterestedInAccept = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT; boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT; boolean isInterestedInRead = interestSet & SelectionKey.OP_READ; boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;
首先“~”符号代表按位取反,“&”代表按位与
selectionKey.interestOps()得到当前的,我们不管他是多少,假设是write 也就是0000 0100 ,而read按位取反后就是1111 1110 ,与上面的进行“&”操作
read的取反一共八位,七位为1 一位为0,为1的与先前的write进行“&”操作则保持不变,原来是多少就是多少,0与前面的“&”操作就是置0
2)key.readyOps()
/创建ready集合的方法 int readySet = selectionKey.readyOps(); //检查这些操作是否就绪的方法 key.isAcceptable();//是否可读,是返回 true boolean isWritable()://是否可写,是返回 true boolean isConnectable()://是否可连接,是返回 true boolean isAcceptable()://是否可接收,是返回 true
3、Selector维护的三种类型SelectionKey集合:
-
已注册的键的集合(Registered key set)
所有与选择器关联的通道所生成的键的集合称为已经注册的键的集合。并不是所有注册过的键都仍然有效。这个集合通过 keys() 方法返回,并且可能是空的。这个已注册的键的集合不是可以直接修改的;试图这么做的话将引发java.lang.UnsupportedOperationException。
-
已选择的键的集合(Selected key set)
所有与选择器关联的通道所生成的键的集合称为已经注册的键的集合。并不是所有注册过的键都仍然有效。这个集合通过 keys() 方法返回,并且可能是空的。这个已注册的键的集合不是可以直接修改的;试图这么做的话将引发java.lang.UnsupportedOperationException。
-
已取消的键的集合(Cancelled key set)
已注册的键的集合的子集,这个集合包含了 cancel() 方法被调用过的键(这个键已经被无效化),但它们还没有被注销。这个集合是选择器对象的私有成员,因而无法直接访问。
4、通过Selector的select()方法可以选择已经准备就绪的通道
int select():阻塞到至少有一个通道在你注册的事件上就绪了。 int select(long timeout):和select()一样,但最长阻塞时间为timeout毫秒。 int selectNow():非阻塞,只要有通道就绪就立刻返回。
select()方法返回的int值表示有多少通道已经就绪,是自上次调用select()方法后有多少通道变成就绪状态。
一旦调用select()方法,并且返回值不为0时,则 可以通过调用Selector的selectedKeys()方法来访问已选择键集合 。
如下:
Set selectedKeys=selector.selectedKeys();
进而可以放到和某SelectionKey关联的Selector和Channel。如下所示:
Set selectedKeys = selector.selectedKeys(); Iterator keyIterator = selectedKeys.iterator(); while(keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); 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(); }
5. 停止选择的方法
选择器执行选择的过程,系统底层会依次询问每个通道是否已经就绪,这个过程可能会造成调用线程进入阻塞状态,那么我们有以下三种方式可以唤醒在select()方法中阻塞的线程。
(1)wakeup()方法 :通过调用Selector对象的wakeup()方法让处在阻塞状态的select()方法立刻返回该方法使得选择器上的第一个还没有返回的选择操作立即返回。如果当前没有进行中的选择操作,那么下一次对select()方法的一次调用将立即返回
(2)close()方法 :通过close()方法关闭Selector, 该方法使得任何一个在选择操作中阻塞的线程都被唤醒(类似wakeup()),同时使得注册到该Selector的所有Channel被注销,所有的键将被取消,但是Channel本身并不会关闭。
原文链接:https://www.cnblogs.com/snailclimb/p/9086334.html