所有文章
https://www.cnblogs.com/lay2017/p/12901123.html
正文
Java NIO选择器(selector)是一个可以监控一个或多个Channel的组件,监控Channel是否可以read或者write操作。这是一种使得单线程可以管理多个Channel的方法,因此NIO可以使用更少的线程来管理更多的网络连接。
为什么使用selector?
使用单个线程处理多个Channel,可以让你节省线程资源。线程资源的创建、销毁、上下文切换是会增加消耗的。因此,我们需要使用更少的线程来做优化。使用选择器可以做到节省线程,如图
创建一个选择器
创建一个选择器通过调用open方法
Selector selector = Selector.open();
注册Channel到选择器当中
为了在selector中选择Channel,你需要先将Channel注册到selector当中。通过register方法
channel.configureBlocking(false); SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
Channel必须是非阻塞模式,否则将不生效。
注意到register方法的第二个参数,指明了你要监听该Channel的什么事件:
1.Connect:连接成功
2.Accept:ServerChannelSocket收到连接
3.Read:可以读取
4.Write:可以写入
事件对应的参数分别是
1.SelectionKey.OP_CONNECT
2.SelectionKey.OP_ACCEPT
3.SelectionKey.OP_READ
4.SelectionKey.OP_WRITE
如果你想监听多个事件,那么可以这样
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
SelectionKey
当你把一个Channel注册到Selector当中,register方法会返回一个SelectionKey对象。这个SelectionKey对象包含了一些东西,如下:
1.interest set
2.ready set
3.channel
4.selector
5.attached object(可选)
下面一一说明
Interest Set
interest set是一个事件集合,包含了selector你响应监听的事件。你可以通过以下方式来判断哪些事件是你关注的
int interestSet = selectionKey.interestOps(); boolean isInterestedInAccept = interestSet & SelectionKey.OP_ACCEPT; boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT; boolean isInterestedInRead = interestSet & SelectionKey.OP_READ; boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;
Ready Set
ready set包含了哪些操作你可以执行,这可能会是你比较常用的,如
int readySet = selectionKey.readyOps();
你可以和interest set一样通过readySet值的位与来得出哪些操作可以执行,也可以直接调用方法
selectionKey.isAcceptable(); selectionKey.isConnectable(); selectionKey.isReadable(); selectionKey.isWritable();
Channel和Selector
Channel channel = selectionKey.channel(); Selector selector = selectionKey.selector();
Attaching Object
你可以把一个对象关联到SelectionKey。例如你可以把Channel对应的Buffer关联,或者把聚合数据对象关联
selectionKey.attach(theObject); Object attachedObj = selectionKey.attachment();
你还可以在register的时候关联
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
通过Selector选择Channel
一旦你把Channel都注册到了Selector当中,你就可以调用select方法。select方法如
1.int select():阻塞等待,直到至少一个Channel响应事件
2.int select(long timeout):和select类似,但是增加了一个最大超时时间
3.int selectNow():不阻塞,直接返回
int返回值返回的是当前有多少个Channel有响应事件。注意,它返回的是你上一次调用select到当前时间有多少个。例如:
你调用了一次select,返回1,表示一个Channel响应。然后你再调用select,又返回1,表示一个新的Channel响应。如果第一个Channel响应的时候,你没有做任何事情那么当前是有两个Channel响应,但是select只返回了1。
selectedKeys
select方法只知道当前有新的Channel响应事件,但不知道总共有多少个。可以通过调用selectedKeys来获取总共多少个
Set<SelectionKey> selectedKeys = selector.selectedKeys();
当你注册一个Channel到Selector,register方法会返回SelectionKey。其实,你可以通过selectedKeySet方法获取,然后,你可以迭代SelectionKey,去操作准备好的Channel
Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> 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(); }
注意一下remove方法,Selector本身并没有移除SelectionKey的实现。你需要代替Selector来做这件事。当你处理完Channel以后,你需要调用remove来移除。而如果这个Channel又响应事件,它会重新添加SelectedKey。
wakeUp
当一个线程调用了select方法以后会被阻塞,那么如果你想停止这种阻塞你可以启动另外一个线程调用Selector.wakeup()方法,那么当前阻塞的线程都将不阻塞,并且新的线程调用select方法也不会阻塞
close
如果你想关闭Selector,那么就调用close方法。这个方法会使得所有SelectionKey失效。但是注意,Channel并没有关闭。
Selector的完整示例
下面是一个完整示例
// 创建一个Selector Selector selector = Selector.open(); // Channel设置为非阻塞 channel.configureBlocking(false); // channel注册到Selector,监听read事件 SelectionKey key = channel.register(selector, SelectionKey.OP_READ); // 死循环 while(true) { // 开始select操作 int readyChannels = selector.selectNow(); // 如果没有Channel响应,跳过 if(readyChannels == 0) continue; // 有Channel响应,获取SelectionKey Set<SelectionKey> selectedKeys = selector.selectedKeys(); // 获取迭代器 Iterator<SelectionKey> keyIterator = selectedKeys.iterator(); // 遍历SelectionKey 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 } // 移除当前SelectionKey keyIterator.remove(); } }