七、Java NIO 选择器

所有文章

https://www.cnblogs.com/lay2017/p/12901123.html

 

正文

Java NIO选择器(selector)是一个可以监控一个或多个Channel的组件,监控Channel是否可以read或者write操作。这是一种使得单线程可以管理多个Channel的方法,因此NIO可以使用更少的线程来管理更多的网络连接。

为什么使用selector?

使用单个线程处理多个Channel,可以让你节省线程资源。线程资源的创建、销毁、上下文切换是会增加消耗的。因此,我们需要使用更少的线程来做优化。使用选择器可以做到节省线程,如图

七、Java NIO 选择器

 

 

 创建一个选择器

创建一个选择器通过调用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();
  }
}

 

上一篇:hadoop的文件操作整理java


下一篇:(三:NIO系列) Java NIO Selector