Java NIO之选择器分析

目录

简介

选择器Selector

选择键SelectionKey

通过Selector选择通道

案例

 


简介

传统的IO流请求都是一个请求需要一个线程来处理,如果请求数量比较庞大,那么对于操作系统来说,线程占用一定的内存,线程上下文切换开销也很大。选择器Selector是java NIO中用来检测多个通道的就绪情况(是否准备好读写),使用单个线程可以管理多个通道。线程可以休眠,直到注册到选择器上的通道处于就绪状态或者周期性轮询检测是否有通道处于就绪状态。一个或多个可选择的通道注册到选择器对象上,将会返回一个表示选择器和通道的键,选择键对象里面包含了对应的通道和选择器。需要注意的是选择器与通道一起使用,通道必须是可选择的(即继承SelectableChannel类),非阻塞模式,所以FileChannel无法与选择器Selector一起使用,所有的套接字通道都可以。

选择器Selector

1.创建

选择器对象可以通道调用静态方法open()创建实例.

Selector selector = Selector.open();

2.注册通道

可选择通道(SelectableChannel)包含所有套接字通道,不包含FileChannel通道可以注册到选择器上面,同时可以指定感兴趣的操作。通道注册到选择器之前,需要将通道设置成非阻塞式的。可选择通道里面定义register()和设置阻塞模式的方法,管理这些通道的真正的是选择器,并且也会管理表示选择器和通道的键。

channel.configureBlocking(false);
SelectionKey key = channel.register(selector,int ops);

对于register()方法中的第二个参数,表示的是感兴趣的操作,选择器监视通道感兴趣的事件,包含了四种不同类型的事件或者操作:读(read),写(write),连接(connect),接受(accept),并不是所有的操作可以在所有的通道上使用,比如:SocketChannel不支持accept()操作。调用通道中的方法validOps()方法可以获取通道可支持的操作,四种不同操作用选择键SelectionKey中四个常量来表示的。

  • SelectionKey.OP_CONNECT.
  • SelectionKey.OP_ACCEPT.
  • SelectionKey.OP_READ.
  • SelectionKey.OP_WRITE.

对于两种及以上的感兴趣的事件,可以使用位操作“或”连接。

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE

3.关闭

当不在使用选择器时,调用close()释放所有占用的资源并将所有的选择键设置为无效。

4.wakeUp()

某个线程调用select()方法后阻塞了,在没有通道已经就绪的情况下,使用别的线程在第一个线程调用select()方法的那个对象上面调用wakeUp(),可以使阻塞在select()方法上的线程立即返回。

选择键SelectionKey

任何一个通道和选择器的注册关系被封装在了SelectionKey对象里面。选择键对象包含如下的属性:

  • interest集合
  • ready集合
  • channel和selector
  • 附加对象

1.interest集合

用于表示通道和选择器组合体所关系的对象,当前的interest集合可以通过调用键对象中interestOps()方法获取,可以通过interest集合和指定选择键常量进行“位与”操作,查看interest集合中是否包含某个确定的事件。

int interestSet = selectionKey.interestOps();
boolean isInterestedInAccept = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;

2.ready集合

表示通道准备好要执行的操作,通过调用键对象中方法readOps()获取相关通道已经就绪的操作,ready集合是interest集合的子集,并且表示interest集合中上次调用select()后就绪的操作。可以通过interest集合与指定的操作进行“位与”操作查看通道中相关操作就绪,比如selectionKey.readOps() &SelectionKey.OP_WRITE来查看ready集合中是否包含读操作。不过选择键SelectionKey对象中提供了四个方法,如下:

int readySet = selectionKey.readyOps()
selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();

3.channel和selector

选择键对象里面可以获取注册的通道和选择器。

Channel  channel  = selectionKey.channel();
Selector selector = selectionKey.selector();

4.附加对象

选择键对象中提供了方法允许向选择键中添加附件,并在后面获取它,是一种将任意对象与键关联的便捷方法。

selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();

此外SelectableChannel也提供了一个register()方法的重载版本可以进行接收一个Object类型的参数。通道注册到选择键上的时候可以直接添加附件。

SelectionKey key = channel.register (selector, SelectionKey.OP_READ, myObject);
等价于
SelectionKey key = channel.register (selector, SelectionKey.OP_READ);
key.attach (myObject)

通过Selector选择通道

public abstract int select( ) throws IOException;
public abstract int select (long timeout) throws IOException;
public abstract int selectNow( ) throws IOException;
public abstract Set keys( )
public abstract Set selectedKeys( )
  • select()-----阻塞直到有一个通道在注册的事件上就绪。
  • select(long timeout)-----阻塞时间到timeout毫秒后,不会一直阻塞。
  • selectNow()-----完全非阻塞模式,不管什么通道会立即返回。如果没有通道就绪,会直接返回0。
  • keys()-----与选择器关联的已经注册键的集合,并不是所有注册过的键都有效,通过keys()方法可以查看。
  • selectedKeys()-----已注册键的子集,集合中的从成员都是相关的通道被选择器判定为已就绪的通道。

select()方法返回的int值表示有多少通道准备就绪,不是已经准备好的所有通道的总数,而是上次调用过select()之后有多少个通道就绪了。之前调用select()就绪的通道,并不会在本次调用已就绪中通道计数中被计入。

通过selector中的方法selectedKeys()方法返回的“已选择键的集合”,表示是已就绪的通道集合。跟ready集合不是同一个概念,ready集合表示的是一个通道准备好的操作。如下就是遍历选择键,然后在一个选择键查看对应的就绪的操作。

Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
while(true) {
  int readyChannels = selector.select();
  if(readyChannels == 0) continue;
  Set selectedKeys = 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();
  }

案例

public class SocketChannelClient {
  private Selector selector=null;
  
  private SocketChannelClient initChannel() throws IOException, ClosedChannelException {
   //打开通道
    SocketChannel channel = SocketChannel.open();
    //配置成非阻塞模式
    channel.configureBlocking(false);
    //连接到端口1234
    channel.connect(new InetSocketAddress(1234));
     //创建选择器
    selector = Selector.open();
    //注册通道到选择器上面, SelectionKey.OP_CONNECT操作
    channel.register(selector, SelectionKey.OP_CONNECT);
    return this;
  }
  public static void main(String[] args) throws IOException {
    new SocketChannelClient().initChannel().listen();
  }
  
  private ByteBuffer buffer = ByteBuffer.allocate(1024);
  
  private void listen() throws IOException {
    System.out.println("客户端监听....");
      //轮询selector
    while(true) {
      selector.select();
        //遍历所有的可选择键的集合
      Iterator iterator = selector.selectedKeys().iterator();
      while(iterator.hasNext()) {
        SelectionKey key = iterator.next();
        //监控到连接事件,向通道写入信息,回复客服端
        if(key.isConnectable()) {
          SocketChannel channel = (SocketChannel)key.channel();
          if(channel.isConnectionPending()) {
            channel.finishConnect();
          }
          channel.configureBlocking(false);
          channel.write(writeInfoToServer("this is information from client..."));
          //向选择器上面注册通道,SelectionKey.OP_READ操作
          channel.register(selector, SelectionKey.OP_READ);
          //监控可读事件,接收服务端的信息
        }else if(key.isReadable()) {
          receiveInfoFromServer(key);
        }
        //移除之前已经处理过的事件
        iterator.remove();
      }
    }
  }
  private void receiveInfoFromServer(SelectionKey key) throws IOException {
    SocketChannel channel = (SocketChannel)key.channel();
    buffer.clear();
    int count;
    while((count= channel.read(buffer))>0) {
      buffer.flip();
      String info = new String(buffer.array());
      System.out.println("receive information from Server:"+info);
    }
  }
  private static ByteBuffer writeInfoToServer(String info) throws UnsupportedEncodingException {
   return ByteBuffer.wrap(info.getBytes("UTF-8"));
  }
}
public class SocketChannelServer {
  private static final int port = 1234;
  Selector selector = null;
  
  private SocketChannelServer initSocketChannel() throws IOException, ClosedChannelException {
    //打开一个ServerSocketChannel来监听
    ServerSocketChannel socketChannel = ServerSocketChannel.open();
    ServerSocket serverSocket = socketChannel.socket();
    //配置成非阻塞模式
    socketChannel.configureBlocking(false);
    serverSocket.bind(new InetSocketAddress(port));
     //创建一个选择器Selector
    selector = Selector.open();
    //将通道注册到选择器上面
    socketChannel.register(selector, SelectionKey.OP_ACCEPT);
    return this;
  }

  public static void main(String[] args) throws IOException {
    new SocketChannelServer().initSocketChannel().listen();
  }
  private void listen() throws IOException {
    System.out.println("Listening on port "+port);
    //轮询selector
    while(true) {
      int select = selector.select();
      if(select==0) {
        continue;
      }
      //遍历所有已选择键
      Iterator iterator = selector.selectedKeys().iterator();
      while(iterator.hasNext()) {
        SelectionKey key = iterator.next();
        //监控到接收连接操作
        if(key.isAcceptable()) {
          ServerSocketChannel  serverChannel =(ServerSocketChannel) key.channel();
          SocketChannel channel = serverChannel.accept();
          channel.configureBlocking(false);
          channel.register(selector, SelectionKey.OP_READ);
          sayHello(channel);
        }
         //监控到可读操作时,接收客户端的信息
        if(key.isReadable()) {
          readInfoFromClient(key);
        }
        iterator.remove();
      }
    }
  }
 
  private void readInfoFromClient(SelectionKey key) throws IOException {
   SocketChannel channel = (SocketChannel)key.channel();
   int count;
   buffer.clear();
   while((count= channel.read(buffer))>0) {
     buffer.flip();
     String info = new String(buffer.array());
     System.out.println("receive information from client: "+info);
   }
  }
  private ByteBuffer buffer = ByteBuffer.allocate(1024);
  private void sayHello(SocketChannel channel) throws IOException {
    buffer.clear();
    buffer.put("welcome to here!\n\r".getBytes());
    buffer.flip();
    channel.write(buffer);
  }
}

 

你可能感兴趣的:(Java,NIO)