当前位置:首页(Home) >> 新闻资源(News List)

时间:2015-03-20 来源:

【JAVA】【NIO】7、JavaNIOSelector 【编程语言】

selector是Java NIO的组件可以检查一个或多个NIO的channel,并且决定哪个channel是为读写准备好了。这种方式,单个线程可以管理多个channel,也就是多个网络连接。

为什么使用选择器 优点就是更少的线程去处理多个通道。实际上,你可以使用一个线程去处理所有的通道。操作系统中线程的切换是很费资源的,而且每个线程本身也占用了一些资源(内存)。所以使用的线程越少越好!

close() 用完Selector要调用close方法。关闭Selector并且将所有注册在selector上的键集作废。通道自己不会关闭。

SelectionKey.channel()方法返回的channel需要转型成你需要的,例如ServerSocketChannel,SocketChannel等。

在文章靠后将会回到兴趣集来讲解。

完整实例 package nio; import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.util.Iterator; import java.util.Set; public class SelectorDemo { public static void main(String[] args) throws IOException { //打开服务器套接字通道 ServerSocketChannel ssc = ServerSocketChannel.open(); //非阻塞模式 ssc.configureBlocking(false); //获取与此通道关联的服务器套接字 ServerSocket ss = ssc.socket(); //服务绑定 ss.bind(new InetSocketAddress(8990)); //打开一个选择器 Selector selector = Selector.open(); //在该选择器上注册通道事件 SelectionKey registerKey = ssc.register(selector, SelectionKey.OP_ACCEPT); while(true) { int readyChannels = selector.select(); if(readyChannels==0) { System.out.println("No Channel Is Ready !"); continue; } Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> keyIterator = selectedKeys.iterator(); while(keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if(key.isAcceptable()) { System.out.println("接收操作!"); }else if(key.isConnectable()) { System.out.println("连接操作!"); }else if(key.isReadable()) { System.out.println("读操作!"); }else if(key.isWritable()) { System.out.println("写操作!"); } keyIterator.remove(); } } } } 以上代码注意,在注册事件时只能是ACCEPT,其它事件在外面注册都会导致程序运行失败,因为其它所有事件都是在ACCEPT后才能够注册的,所以要注意这一点。

注册通道 channel.configureBlocking(false);配置通道为非阻塞模式 channel.register(selector,SelectionKey.OP_ACCEPT);通过该方法注册

一个通道触发了一个事件意思就是对该事件准备就绪了。所以,一个channel和服务器连接成功了就是连接就绪。ServerSocketChannel接受了连接就是接受就绪。一个通道有数据准备好被读了就是读就绪。一个通道准备写入数据就是写就绪。 这四中事件通过SelectionKey的四个常量来定义: 1、SelectionKey.OP_CONNECT 2、SelectionKey.OP_ACCEPT 3、SelectionKey.OP_READ 4、SelectionKey.OP_WRITE 如果你对多个事件有兴趣,可以如下来写:

Ready Set ready集合是channel为哪些操作已经就绪了。在一次选择后,你会首先访问ready集合,如下:

Set selectedKeys = selector.selectedKeys();

当你注册了一个通道事件时会返回一个SelectionKey对象。这个对象表示注册到该Selector上的通道。你可以通过selectedKeySet访问这些keys。如下:

selectionKey.attach(theObject);

Attaching Objects 可以将一个对象或者更多信息附加到SelectionKey上以便识别一个具体的通道。例如,你可以附加和通道一起使用的Buffer或者一个包含很多聚集数据的对象,如下:

selectionKey.isAcceptable(); selectionKey.isConnectable(); selectionKey.isReadable(); selectionKey.isWritable();

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集合。并检测各个键对应的通道就绪事件。 注意每次迭代末尾的keyIterator.remove()调用。Selector不会自己从已选择键集中移除SelectionKey实例。必须在处理完通道时自己移除。下次该通道变成就绪时,Selector会再次将其放入已选择键集中。

int interestSet = selectionKey.interestOps();

使用selector,channel必须是非阻塞模式。意味着FileChannel不用使用selector,因为FileChannel不能转为非阻塞模式。SocketChannel可以正常使用。

现在的操作系统和CPU在多任务上变得越来越好,所以多线程的开销也变得更小了。事实上,如果一个CPU有多个核心,不用多线程可能是一种浪费。不管怎么说,设计讨论应该是在另一篇文章说。在这里,知道用selector,单线程去处理多通道就足够了。

Interest Set interest集合是你感兴趣的事件集合。你可以通过SelectionKey读写兴趣集合,如下:

你可以通过&操作找出某个事件是否是兴趣集合的。

Channel channel = selectionKey.channel();

Selector selector = selectionKey.selector();

int readySet = selectionKey.readyOps();

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

Object attachedObj = selectionKey.attachment();

下一节:等待

同样你可以像上面提到的方法一样通过&来测试哪些操作已经就绪了。但是同样你可以通过如下方法来得到:

SelectionKey’s 正如前面所述,当你通过selector给通道注册,register方法将返回一个SelectionKey对象,这个对象包含了一些你感兴趣的属性: ·The interest set ·The ready set ·The Channel ·The Selector ·An attached object (optional)

selectedKeys() 一旦你调用了一个select()方法并且返回值,表明一个或多个通道就绪了,你可以通过selected key集合访问就绪通道,通过调用selectedKeys方法实现,如下:

wakeUp() 一个线程调用select方法阻塞了,即使没有就绪通道,也可以让select方法返回。让其它线程通过刚刚调用select方法的Selector对象调用wakeup方法即可。阻塞在select方法上的线程会立即返回。 如果其它线程调用了wakeup,但是当前没有线程阻塞在select,那么下一个调用select方法的线程会立即唤醒。

Selecting Channels via a Selector 一旦你通过selector注册了多个通道,你可以调用selecto方法。这些方法返回为兴趣集合(连接,接收,读,写)事件就绪的通道。换言之,如果你对对读就绪的通道感兴趣, 你就会通过select方法返回读就绪的通道。 以下是select方法: ·int select() ·int select(long timeout) ·int selectNow() select()方法会阻塞直到至少一个通道是为你注册的事件就绪的。 select(long timeout)和select()一样,除了它阻塞会有一个超时时间。 selectNow()没有阻塞,无论什么通道,就绪就立刻返回。 select()方法返回的int表示有多少通道就绪了,即自从最后一次调用select()方法以来,有多少通道就绪了。如果你调用select方法返回1,说明有一个通道就绪了,你再次调用返回1,说明另一个通道就绪了。如果你对第一个就绪的通道什么都不做,你现在就有两个就绪通道,但是仅仅只有一个通道就绪在每次select方法调用过程中。

你也可以再register的时候就附加对象

创建选择器 通过调用Selector.open()方法创建。

Channel + Selector 通过SelectionKey访问channel+selector很简单,如下:

注意register方法的第二个参数。这是一个兴趣集合,意思是通过selector监听这个channel时,对什么样的事件感兴趣。有如下几种: 1、Connect 2、Accept 3、Read 4、Write

boolean isInterestedInAccept = interestSet & SelectionKey.OP_ACCEPT; boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT; boolean isInterestedInRead = interestSet & SelectionKey.OP_READ; boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

$(function () { $('pre.prettyprint code').each(function () { var lines = $(this).text().split('\n').length; var $numbering = $('').addClass('pre-numbering').hide(); $(this).addClass('has-numbering').parent().append($numbering); for (i = 1; i ').text(i)); }; $numbering.fadeIn(1700); }); });

点击次数:6640
作者: