Java NIO Selector是一个组件,可以检查一个或多个Java NIO Channel实例,并确定那些通道可供使用,read或write。这样,单个线程可以管理多个Channel,从而可以管理多个网络连接。
仅使用单个线程来处理多个Channel的优点是:只需要更少的线程来处理Channel。实际上,你可以使用一个线程来处理所有Channel。线程之间的切换对于操作系统而言是昂贵的,并且每个线程也占用操作系统中的一些资源(内存)。因此,使用的线程越少越好。
但是请记住,现代操作系统和CPU在多任务处理方面变得越来越好,因此多线程的开销随着时间的推移会变得越来越小。实际上,如果一个CPU有多个内核,则可能由于不执行多任务处理而浪费了CPU性能。
这是单线程使用Selector处理3个Channel的图示:
你可以通过调用Selector.open()方法创建一个Selector,如下所示:
Selector selector = Selector.open();
为了将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;
当向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);
使用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:
SetselectedKeys = selector.selectedKeys();
您可以迭代此集合以访问就绪Channel:
SetselectedKeys = 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(); }
此循环迭代会测试Channel已就绪的事件。
注意,每次迭代结束时都会调用keyIterator.remove(),Selector不会从集合中删除SelectionKey实例,处理完Channel后,必须执行此操作。下次Channel变为“就绪”状态时,Selector将再次将其添加到集合中。
由SelectionKey.channel()方法返回的Channel应强制转换为你需要使用的Channel,例如ServerSocketChannel或SocketChannel等。
即使尚未准备好任何Channel,也可以使已调用被阻塞的select()方法的线程离开select()方法。这是通过让另一个线程在Selector上调用Selector.wakeup()方法来完成的,在select()中等待的线程将立即返回。
如果其他线程调用了wakeup(),而select()内当前没有任何线程被阻塞,则下一个调用select()的线程将立即“唤醒”。
Selector完成后,将调用其close()方法。这将关闭Selector,并使在此选择器中注册的所有SelectionKey实例无效,但Channel本身未关闭。
这是一个完整的示例,打开一个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; SetselectedKeys = 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(); } }
原文地址: https://www.zhblog.net/go/java/tutorial/java-nio-selector?t=614