www.zhblog.net

Java NIO Selector

Java NIO Selector是一个组件,可以检查一个或多个Java NIO Channel实例,并确定那些通道可供使用,read或write。这样,单个线程可以管理多个Channel,从而可以管理多个网络连接。


1.为什么要使用Selector?

仅使用单个线程来处理多个Channel的优点是:只需要更少的线程来处理Channel。实际上,你可以使用一个线程来处理所有Channel。线程之间的切换对于操作系统而言是昂贵的,并且每个线程也占用操作系统中的一些资源(内存)。因此,使用的线程越少越好。

但是请记住,现代操作系统和CPU在多任务处理方面变得越来越好,因此多线程的开销随着时间的推移会变得越来越小。实际上,如果一个CPU有多个内核,则可能由于不执行多任务处理而浪费了CPU性能。


这是单线程使用Selector处理3个Channel的图示:

overview-selectors.png


2.创建一个Selector

你可以通过调用Selector.open()方法创建一个Selector,如下所示:

Selector selector = Selector.open();


3.在Selector中注册Channel

为了将Channel与Selector一起使用,必须在Selector中注册Channel。使用SelectableChannel.register()方法,如下所示:

channel.configureBlocking(false);

SelectionKey key = channel.register(selector, SelectionKey.OP_READ);


Channel必须处于非阻塞模式才能与Selector一起使用。这意味着你不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式,Socket Channel才可以。

注意register()方法的第二个参数,表示你希望通过Selector在Channel中监听那些事件。可以监听四种不同的事件:

Connect

Accept

Read

Write

一个Channel触发事件也可以说准备某事件 。因此,已成功连接到服务器的Channel是“connect ready”。接受连接的服务器socket Channel是“accept ready ”。准备读取数据的Channel是“read ready”。准备好向其写入数据的Channel是“write ready”。

这四个事件由四个SelectionKey常量表示:

SelectionKey.OP_CONNECT

SelectionKey.OP_ACCEPT

SelectionKey.OP_READ

SelectionKey.OP_WRITE

如果想要监听多个事件,如下所示:

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;    


4.SelectionKey

当向Selector注册Channel时,register()方法将返回SelectionKey对象,这个SelectionKey对象包含一些属性:

interest set

ready set

Channel

Selector

attached object

下面分别进行描述


Interest Set

interest set是监听的事件集,你可以通过SelectionKey读取和写入,如下所示:

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;   


可以将interest set与给定的SelectionKey常数进行“与”运算,以找出某个事件是否在interestSet中。


Ready Set

ready set是Channel "ready"的一组操作,选择后,将主要访问就绪集。可以按以下方式访问就绪集:

int readySet = selectionKey.readyOps();


你可以用interest set相同的方式测试Channel准备就绪的事件。你也可以改用这四个方法,它们都返回一个布尔值:

selectionKey.isAcceptable();

selectionKey.isConnectable();

selectionKey.isReadable();

selectionKey.isWritable();


Channel、Selector

从SelectionKey访问Channel和Selector很简单:

Channel  channel  = selectionKey.channel();

Selector selector = selectionKey.selector();   


Attaching Objects

你可以将一个对象附加到SelectionKey,这是识别给定Channel或将更多信息附加到该Channel的便捷方法。例如,将正在使用的Buffer附加到Channel,或包含更多聚合数据的对象。这是附加对象的方式:

selectionKey.attach(theObject);

Object attachedObj = selectionKey.attachment();


你还可以在register()方法中向Selector注册Channel时已经附加对象:

SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);


5.通过Selector选择Channel

使用Selector注册一个或多个Channel后,你可以调用select()方法。这些方法返回你监听的事件(connect, accept, read or write)的“ready”Channel。换句话说,如果你监听了Channel的读事件,select()方法将告知你准备读的Channel。

select相关方法:

int select() 

int select(long timeout) 

int selectNow()

select()会阻塞,直到至少有一个Channel可用于你注册的事件。

select(long timeout)与select()类似,它会阻塞直到超时(毫秒)。

selectNow()完全不阻塞,无论Channel是否准备好,它都会立即返回。


select()方法返回的int告诉你多少个Channel ready。也就是说,自从你上次调用select()后已准备好了多少个Channel。如果调用select()返回1,则一个Channel已准备好了;再调用一次select(),若有一个Channel已准备好,则它将再次返回1。


selectedKeys()

一旦调用了select()方法,并且其返回值指示一个或多个Channel已就绪,则可以通过调用Selector的selectedKeys()方法来访问就绪Channel:

Set<SelectionKey> selectedKeys = selector.selectedKeys();   


您可以迭代此集合以访问就绪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();

}


此循环迭代会测试Channel已就绪的事件。

注意,每次迭代结束时都会调用keyIterator.remove(),Selector不会从集合中删除SelectionKey实例,处理完Channel后,必须执行此操作。下次Channel变为“就绪”状态时,Selector将再次将其添加到集合中。

由SelectionKey.channel()方法返回的Channel应强制转换为你需要使用的Channel,例如ServerSocketChannel或SocketChannel等。


6.wakeUp()

即使尚未准备好任何Channel,也可以使已调用被阻塞的select()方法的线程离开select()方法。这是通过让另一个线程在Selector上调用Selector.wakeup()方法来完成的,在select()中等待的线程将立即返回。

如果其他线程调用了wakeup(),而select()内当前没有任何线程被阻塞,则下一个调用select()的线程将立即“唤醒”。


7.close()

Selector完成后,将调用其close()方法。这将关闭Selector,并使在此选择器中注册的所有SelectionKey实例无效,但Channel本身未关闭。


8.完整Selector实例

这是一个完整的示例,打开一个Selector,向其注册一个Channel(忽略通道实例化),并监听Selector四个事件(accept, connect, read, write)。

Selector selector = Selector.open();



channel.configureBlocking(false);



SelectionKey key = channel.register(selector, SelectionKey.OP_READ);





while(true) {



  int readyChannels = selector.selectNow();



  if(readyChannels == 0) continue;





  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();

  }

}



 

展开阅读全文

评论

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 心情