IO入门002之SelectionKey详解

SelectionKey的作用:

就是Selector和Channel之间的桥梁。即Selector和SelectionKey 的事件产生关系

serverChannel.register(selector, SelectionKey.OP_ACCEPT);

selector 对SelectionKey 1对n

SelectionKey 对Channel:1对1

eg:

1> Selector.select();就Selector获取已经在该Selector注册的SelectionKey事件。没有事件会一直阻塞。

2> 然后通过Selector.selectedKeys()获取所有的SelectionKey  

3>SelectionKey和Channel是一对一的关系。通过SelectionKey + 其事件:获得到Channel对象进而进行读取客户端的发送数据或服务端给客户端回响应

 

源码:

package java.nio.channels;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public abstract class SelectionKey {
    public abstract SelectableChannel channel();

    public abstract Selector selector();

    public abstract boolean isValid();

    public abstract void cancel();

    public abstract int interestOps();

    public abstract SelectionKey interestOps(int ops);

    public abstract int readyOps();

    public static final int OP_READ = 1 << 0;

    public static final int OP_WRITE = 1 << 2;

    public static final int OP_CONNECT = 1 << 3;

    public static final int OP_ACCEPT = 1 << 4;

    public final boolean isReadable() { return (readyOps() & OP_READ) != 0;}

    public final boolean isWritable() {return (readyOps() & OP_WRITE) != 0;}

    public final boolean isConnectable() {return (readyOps() & OP_CONNECT) != 0;}

    public final boolean isAcceptable() {return (readyOps() & OP_ACCEPT) != 0;}
    
    private volatile Object attachment = null;
    private static final AtomicReferenceFieldUpdater
        attachmentUpdater = AtomicReferenceFieldUpdater.newUpdater(
            SelectionKey.class, Object.class, "attachment"
        );

    public final Object attach(Object ob) {return attachmentUpdater.getAndSet(this, ob);}

    public final Object attachment() {return attachment;}
}

主要的实现类:

package sun.nio.ch;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.spi.AbstractSelectionKey;
public class SelectionKeyImpl extends AbstractSelectionKey {
    final SelChImpl channel;
    public final SelectorImpl selector;
    private int index;
    private volatile int interestOps;
    private int readyOps;

    SelectionKeyImpl(SelChImpl var1, SelectorImpl var2) { this.channel = var1;this.selector = var2;}
    public SelectableChannel channel() {return (SelectableChannel)this.channel;}
    public Selector selector() {return this.selector;}
    int getIndex() {return this.index;}
    void setIndex(int var1) {this.index = var1;}
    private void ensureValid() {
        if (!this.isValid()) {throw new CancelledKeyException();}
    }

    public int interestOps() {this.ensureValid();return this.interestOps;}

    public SelectionKey interestOps(int var1) {this.ensureValid();return this.nioInterestOps(var1);}
    public int readyOps() {this.ensureValid(); return this.readyOps;}
    public void nioReadyOps(int var1) {this.readyOps = var1;}
    public int nioReadyOps() {return this.readyOps;}
    public SelectionKey nioInterestOps(int var1) {
        if ((var1 & ~this.channel().validOps()) != 0) {
            throw new IllegalArgumentException();
        } else {
            this.channel.translateAndSetInterestOps(var1, this);
            this.interestOps = var1;
            return this;
        }
    }

    public int nioInterestOps() {return this.interestOps;}
}

通过源码可以看到有5个点:

1> Interest Set兴趣集合
2> Ready Set就绪集合
3> Channel通道
4> Selector选择器
5> Attach附加对象

Interest Set:

对应Selector监听管道的4个事件
这四种事件用SelectionKey的四个常量来表示:
SelectionKey.OP_CONNECT 一个channel成功连接到服务器称为”连接就绪“
SelectionKey.OP_ACCEPT 一个server socket channel服务准备好接收新进入的client连接称为”接收就绪“
SelectionKey.OP_READ   一个有数据可读的通道可以说是”读就绪“

OP_READ 事件不仅仅只有可读时才触发,以下情况也会触发:

channel 中数据读完

连接管道的另一端被关闭

有一个错误的 pending

对法发送消息过来

SelectionKey.OP_WRITE  一个等待写数据的通道可以说是”写就绪“

//通道注册读事件 即把Selector、SelectionKey 和channel建立关系

channel.register(this.selector, SelectionKey.OP_READ);

SelectionKey 对象的有效期间,Selector 会一直监控与 SelectionKey 对象相关的事件,如果事件发生,就会把 SelectionKey 对象加入到 selected-keys 集合中。
在以下情况下,SelectionKey 对象会失效,意味着 Selector 再也不会监控与它相关的事件:
 1> 程序调用 SelectionKey 的cancel()方法
 2> 关闭与 SelectionKey 关联的Channel
 3> 与 SelectionKey 关联的 Selector 被关闭

Ready Set


Ready Set是通道已经准备就绪的操作的集合,在一个选择后,你会是首先访问这个Ready Set。
int readySet = selectionKey.readyOps();  
// 轮询访问selector
while (true) {
    // 当注册的事件到达时,方法返回;否则,该方法会一直阻塞
    selector.select();
    // 获得selector中选中的项的迭代器,选中的项为注册的事件
    Iterator ite = this.selector.selectedKeys().iterator();
    while (ite.hasNext()) {
        SelectionKey key = (SelectionKey) ite.next();
        // 删除已选的key,以防重复处理
        ite.remove();

        // 客户端请求连接事件
        if (key.isAcceptable()) {
            handlerAccept(key);
            // 获得了可读的事件
        } else if (key.isReadable()) {
            handelerRead(key);
        }
    }
}

Channel通道


通过SelectionKey对象获取其监听的Channel通道:Channel channel = selectionKey.channel();  


Selector选择器


通过SelectionKey对象获取其所属的选择器Selector:Selector selector = selectionKey.selector();



Attach附加对象


可以将一个对象或者更多的信息附着到SelectionKey上,这样就能方便的识别某个给定的通道。

例如,可以附加与通道一起使用的Buffer,或是包含聚集数据的某个对象。

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

测试例子

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

/**
 * NIO服务端
 * 
 * @author -琴兽-
 */
public class NIOServer {
   // 通道管理器
   private Selector selector;

 //启动服务端测试 
  public static void main(String[] args) throws IOException { 
         NIOServer server = new NIOServer(); 
         server.initServer(8080); server.listen(); 
   }

   //获得一个ServerSocket通道,并对该通道做一些初始化的工作
   public void initServer(int port) throws IOException {
      // 获得一个ServerSocket通道
      ServerSocketChannel serverChannel = ServerSocketChannel.open();
      // 设置通道为非阻塞
      serverChannel.configureBlocking(false);
      // 将该通道对应的ServerSocket绑定到port端口
      serverChannel.socket().bind(new InetSocketAddress(port));
      // 获得一个通道管理器
      this.selector = Selector.open();
      // 将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后,
      // 当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。
      serverChannel.register(selector, SelectionKey.OP_ACCEPT);
   }

   /**
    * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理
    */
   public void listen() throws IOException {
      System.out.println("服务端启动成功!");
      // 轮询访问selector
      while (true) {
         // 当注册的事件到达时,方法返回;否则,该方法会一直阻塞
         selector.select();
         // 获得selector中选中的项的迭代器,选中的项为注册的事件
         Iterator ite = this.selector.selectedKeys().iterator();
         while (ite.hasNext()) {
            SelectionKey key = (SelectionKey) ite.next();
            // 删除已选的key,以防重复处理
            ite.remove();

            handler(key);
         }
      }
   }

   /**
    * 处理请求
    */
   public void handler(SelectionKey key) throws IOException {
      
      // 客户端请求连接事件
      if (key.isAcceptable()) {
         handlerAccept(key);
         // 获得了可读的事件
      } else if (key.isReadable()) {
         handelerRead(key);
      }
   }

   /**
    * 处理连接请求
    */
   public void handlerAccept(SelectionKey key) throws IOException {
      ServerSocketChannel server = (ServerSocketChannel) key.channel();
      // 获得和客户端连接的通道
      SocketChannel channel = server.accept();
      // 设置成非阻塞
      channel.configureBlocking(false);

      // 在这里可以给客户端发送信息哦
      System.out.println("新的客户端连接");
      // 在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。
      channel.register(this.selector, SelectionKey.OP_READ);
   }

   /**
    * 处理读的事件
    * 
    * @param key
    * @throws IOException
    */
   public void handelerRead(SelectionKey key) throws IOException {
      // 服务器可读取消息:得到事件发生的Socket通道
      SocketChannel channel = (SocketChannel) key.channel();
      // 创建读取的缓冲区
      ByteBuffer buffer = ByteBuffer.allocate(1024);
      int read = channel.read(buffer);
      if(read > 0){
         byte[] data = buffer.array();
         String msg = new String(data).trim();
         System.out.println("服务端收到信息:" + msg);
         
         //回写数据
         ByteBuffer outBuffer = ByteBuffer.wrap("好的".getBytes());
         channel.write(outBuffer);// 将消息回送给客户端
      }else{
         System.out.println("客户端关闭");
         key.cancel();
      }
   }
 
}

你可能感兴趣的:(IO)