Java NIO Selector是一个可以检查一个或多个Java NIO Channel实例的组件,并且决定了哪个channel已经准备好读写。这个方法可以用一个单线程管理多个channel,和多个网络连接。
Why User a Selector
只使用一个单线程去处理多个channel的优势是使用更少的线程去处理channel。实际上,你可以使用一个线程去处理你所有的channel。对于一个操作系统来说进行线程之间的切换是非常昂贵的,并且在操作系统里的每个线程也都携带了一些资源(内存),因此,使用的线程越少越好。
用心记住,现代化的操作系统和CPU变得越来越好在多任务层面,所以多线程的负载比以前要小。实际上,如果一个CPU有多个核心,你如果没有使用多任务也许会浪费CPU的能力。无论如何,设计上的讨论属于其他的内容。这里说的已经足够了,你可以使用单线程处理多个channel,使用一个Selector。
这里是单线程使用一个Selector去处理3个channel的例子:
Java NIO: 一个线程使用一个Selector去处理3个channel
Createing a Selector
可以调用Selector.open()方法来创建一个Selector,像这样:
Selector selector = Selector.open()
Registering Channel with the Selector
为了使用带有Channel的Selector你必须注册Channel到Selector。使用SelectableChannel.register()方法来完成这个,就像下面这个示例代码一样:
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
Channel必须是非阻塞的模式在搭配Selector的时候。这就意味着你不能在Selector里使用FileChannel因为FileChannel不能切换回非阻塞的模式。Socket Channel能很好的使用。
注意register()方法的第二个参数。这个是一个"inter set"意思是你在Channel里监听的什么事件感兴趣,通过Selector。这里有四种你可以监听到的不同的事件:
1、Connect(连接)
2、Accept(接受)
3、Read(读)
4、Write(写)
一个channel“fires an event”也就是说已经"read"为事件。所以,一个channel成功连接到另一个服务器是"connect ready"。一个server socket channel 接受入栈的连接是"accept ready"。一个channel已经准备好读是"read read"。一个channel准备好写是"write ready"。
下面是由四个SelectionKey常量包含的四种事件
1、SelectionKey.OP_CONNECT
2、SelectionKey.OP_ACCEPT
3、SelectionKey.OP_READY
4、SelectionKey.OP_WRITE
如果你对更多事件感兴趣,或者对常量里的几个事件都感兴趣,像这样:
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
这节的后面会进一步说明这个集合。
SelectionKey
跟你在前面章节看到的一样,当你注册一个Channel到Selector的时候,register()方法返回一个SelectionKey对象。这个SelectionKey对象包含了一些感兴趣的属性。
- The interest set
- The ready set
- The Channel
- The Selector
- An attached object(optional)
将在下面的内容描述这些属性。
Interset Set
这个感兴趣的集合是在"selecting"里感兴趣的事件的集合,如同在"Registering Channel with the Selector "描述的一样。你可以使用SelectionKey读写相关的集合像下面这样:
int interestSet = selectionKey.interestOps();
boolean isInterestedInAccept = SelectionKey.OP_ACCEPT == (interests & SelectionKey.OP_ACCEPT);
boolean isInterestedInConnect = SelectionKey.OP_CONNECT == (interests & SelectionKey.OP_CONNECT);
boolean isInterestedInRead = SelectionKey.OP_READ == (interests & SelectionKey.OP_READ);
boolean isInterestedInWrite = SelectionKey.OP_WRITE == (interests & SelectionKey.OP_WRITE);
如同你看到的,你可以用and操作符与给定的SelectionKey常量在interset集合里找出确切的事件。
Ready Set
ready set是已经准备好的channel的集合。在Selection后你可以访问准备好的集合。Selection在后面的内容里进行解释。你可以访问准备好的集合像下面这样:
int readySet = selectionKey.readyOps();
你可以用相同的方法测试interest集合里,channel对于哪些事件/操作已经准备好。但是,你也可以用下面这四个方法替代,所有的都返回一个boolean变量:
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。例如,你可以附加你使用channel的Buffer,或者一个包含更多聚合数的对象。这里展示了附加对象:
selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();
你也可以在注册Channel到Selector的时候附加一个对象,在register()方法。这里是具体的用法:
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
Selecting Channel via a Selector
一旦你注册一个或多个channel到Selector你可以调用select()方法。这些方法返回了你那些感兴趣的并且已经"ready"的Channel(连接,接受,读或者写)。在另一方面,如果你对在channel理已经准备好读的感兴趣,你将收到那个channel,从select()方法收到这个已经准备好读的channel。
这里是select()方法:
- int select()
- int select(long timeout)
- int selectNow()
select()阻塞直到至少有一个channel对于你感兴趣的事件准备好。
select(long timeout) 做的事情跟select()一样除了它的阻塞有一个timeout的最大值(由那个参数决定)。
selectNow()从来不阻塞。它立即返回哪些channel已经准备好。
select()的int返回值告诉有多少channel已经准备好。也就是从上次你调用select()方法之后有多少channel变成就绪状态。如果你调用select()并且它返回1是因为有一个channel变成了就绪态,并且你多次调用select()而且有多个channel它会再次返回1.如果你在第一个channel就绪的状态下没有做任何事,你将有两个就绪的channels,但是只有一个channel变成就绪态在每次调用select()之间。
selectedKeys()
一旦你调用select()其中一个方法并且它的返回值已经指明一个或者多个channel已经就绪,你可以使用"selected key set"来访问就绪的channel,通过调用selectors的selectedKeys()方法。这里是如何调用:
Set<SelectionKey> selectedKeys = selector.selectedKeys();
当你使用Channel.register()方法将channel注册到Selector它返回一个SelectionKey对象。这个key描述了selector的channel注册登记。它是你可以使用selectedKeySet()方法从SelectionKey里访问的key。
你可以枚举这个selected Key集合来访问就绪的channels。这里是怎么使用:
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();
}
这个循环枚举了selected key set里的key。对于每个key它测试key来决定这个key引用的channel已经就绪为了某些事件。
注意keyIterator.remove()在每次枚举的末尾调用。Selector不会从它自己的selected key集合里移除SelectionKey实例。你必须这么做,当你已经处理完channel。下次channel变成“ready”Selector将会再次将它添加到selected key集合。
SelectionKey.channel返回的channel将会被转成你需要的channel,例如ServerSocketChannel或者SocketChannel等等。
wakeUp()
线程调用select()方法会阻塞,可以离开select()方法,即使还没有channel就绪。这个通过不同的线程在第一个线程调用select()的Selector上调用Selector.wakeup()。在select()里等待的线程将会直接返回。
如果不同的线程调用wakeup()并且没有线程正阻塞在select(),下一个调用select()的线程将会立刻"wake up"。
close()
当你完成了Selector之后调用它的close()方法。这个关闭Selector并且将注册到这个Seletor的所有SelectionKey实例失效。channel本身没有关闭。
Full Selector Example
这里是一个完整的例子,打开Selector,注册一个channel到上面(不包含channel的实例化),并且持续监控四种事件准备就绪的Selector(accept,connect,read,write)。
Java NIO Selector是一个可以检查一个或多个Java NIO Channel实例的组件,并且决定了哪个channel已经准备好读写。这个方法可以用一个单线程管理多个channel,和多个网络连接。
Why User a Selector
只使用一个单线程去处理多个channel的优势是使用更少的线程去处理channel。实际上,你可以使用一个线程去处理你所有的channel。对于一个操作系统来说进行线程之间的切换是非常昂贵的,并且在操作系统里的每个线程也都携带了一些资源(内存),因此,使用的线程越少越好。
用心记住,现代化的操作系统和CPU变得越来越好在多任务层面,所以多线程的负载比以前要小。实际上,如果一个CPU有多个核心,你如果没有使用多任务也许会浪费CPU的能力。无论如何,设计上的讨论属于其他的内容。这里说的已经足够了,你可以使用单线程处理多个channel,使用一个Selector。
这里是单线程使用一个Selector去处理3个channel的例子:
Java NIO: 一个线程使用一个Selector去处理3个channel
Createing a Selector
可以调用Selector.open()方法来创建一个Selector,像这样:
Selector selector = Selector.open()
Registering Channel with the Selector
为了使用带有Channel的Selector你必须注册Channel到Selector。使用SelectableChannel.register()方法来完成这个,就像下面这个示例代码一样:
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
Channel必须是非阻塞的模式在搭配Selector的时候。这就意味着你不能在Selector里使用FileChannel因为FileChannel不能切换回非阻塞的模式。Socket Channel能很好的使用。
注意register()方法的第二个参数。这个是一个"inter set"意思是你在Channel里监听的什么事件感兴趣,通过Selector。这里有四种你可以监听到的不同的事件:
1、Connect(连接)
2、Accept(接受)
3、Read(读)
4、Write(写)
一个channel“fires an event”也就是说已经"read"为事件。所以,一个channel成功连接到另一个服务器是"connect ready"。一个server socket channel 接受入栈的连接是"accept ready"。一个channel已经准备好读是"read read"。一个channel准备好写是"write ready"。
下面是由四个SelectionKey常量包含的四种事件
1、SelectionKey.OP_CONNECT
2、SelectionKey.OP_ACCEPT
3、SelectionKey.OP_READY
4、SelectionKey.OP_WRITE
如果你对更多事件感兴趣,或者对常量里的几个事件都感兴趣,像这样:
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
这节的后面会进一步说明这个集合。
SelectionKey
跟你在前面章节看到的一样,当你注册一个Channel到Selector的时候,register()方法返回一个SelectionKey对象。这个SelectionKey对象包含了一些感兴趣的属性。
- The interest set
- The ready set
- The Channel
- The Selector
- An attached object(optional)
将在下面的内容描述这些属性。
Interset Set
这个感兴趣的集合是在"selecting"里感兴趣的事件的集合,如同在"Registering Channel with the Selector "描述的一样。你可以使用SelectionKey读写相关的集合像下面这样:
int interestSet = selectionKey.interestOps();
boolean isInterestedInAccept = SelectionKey.OP_ACCEPT == (interests & SelectionKey.OP_ACCEPT);
boolean isInterestedInConnect = SelectionKey.OP_CONNECT == (interests & SelectionKey.OP_CONNECT);
boolean isInterestedInRead = SelectionKey.OP_READ == (interests & SelectionKey.OP_READ);
boolean isInterestedInWrite = SelectionKey.OP_WRITE == (interests & SelectionKey.OP_WRITE);
如同你看到的,你可以用and操作符与给定的SelectionKey常量在interset集合里找出确切的事件。
Ready Set
ready set是已经准备好的channel的集合。在Selection后你可以访问准备好的集合。Selection在后面的内容里进行解释。你可以访问准备好的集合像下面这样:
int readySet = selectionKey.readyOps();
你可以用相同的方法测试interest集合里,channel对于哪些事件/操作已经准备好。但是,你也可以用下面这四个方法替代,所有的都返回一个boolean变量:
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。例如,你可以附加你使用channel的Buffer,或者一个包含更多聚合数的对象。这里展示了附加对象:
selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();
你也可以在注册Channel到Selector的时候附加一个对象,在register()方法。这里是具体的用法:
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
Selecting Channel via a Selector
一旦你注册一个或多个channel到Selector你可以调用select()方法。这些方法返回了你那些感兴趣的并且已经"ready"的Channel(连接,接受,读或者写)。在另一方面,如果你对在channel理已经准备好读的感兴趣,你将收到那个channel,从select()方法收到这个已经准备好读的channel。
这里是select()方法:
- int select()
- int select(long timeout)
- int selectNow()
select()阻塞直到至少有一个channel对于你感兴趣的事件准备好。
select(long timeout) 做的事情跟select()一样除了它的阻塞有一个timeout的最大值(由那个参数决定)。
selectNow()从来不阻塞。它立即返回哪些channel已经准备好。
select()的int返回值告诉有多少channel已经准备好。也就是从上次你调用select()方法之后有多少channel变成就绪状态。如果你调用select()并且它返回1是因为有一个channel变成了就绪态,并且你多次调用select()而且有多个channel它会再次返回1.如果你在第一个channel就绪的状态下没有做任何事,你将有两个就绪的channels,但是只有一个channel变成就绪态在每次调用select()之间。
selectedKeys()
一旦你调用select()其中一个方法并且它的返回值已经指明一个或者多个channel已经就绪,你可以使用"selected key set"来访问就绪的channel,通过调用selectors的selectedKeys()方法。这里是如何调用:
Set<SelectionKey> selectedKeys = selector.selectedKeys();
当你使用Channel.register()方法将channel注册到Selector它返回一个SelectionKey对象。这个key描述了selector的channel注册登记。它是你可以使用selectedKeySet()方法从SelectionKey里访问的key。
你可以枚举这个selected Key集合来访问就绪的channels。这里是怎么使用:
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();
}
这个循环枚举了selected key set里的key。对于每个key它测试key来决定这个key引用的channel已经就绪为了某些事件。
注意keyIterator.remove()在每次枚举的末尾调用。Selector不会从它自己的selected key集合里移除SelectionKey实例。你必须这么做,当你已经处理完channel。下次channel变成“ready”Selector将会再次将它添加到selected key集合。
SelectionKey.channel返回的channel将会被转成你需要的channel,例如ServerSocketChannel或者SocketChannel等等。
wakeUp()
线程调用select()方法会阻塞,可以离开select()方法,即使还没有channel就绪。这个通过不同的线程在第一个线程调用select()的Selector上调用Selector.wakeup()。在select()里等待的线程将会直接返回。
如果不同的线程调用wakeup()并且没有线程正阻塞在select(),下一个调用select()的线程将会立刻"wake up"。
close()
当你完成了Selector之后调用它的close()方法。这个关闭Selector并且将注册到这个Seletor的所有SelectionKey实例失效。channel本身没有关闭。
Full Selector Example
这里是一个完整的例子,打开Selector,注册一个channel到上面(不包含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();
}
}