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

 

原文地址: https://www.zhblog.net/go/java/tutorial/java-nio-selector?t=614

 

 

 

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值