NIO源码分析:SelectionKey
SelectionKey
SelectionKey,选择键,在每次通道注册到选择器上时都会创建一个SelectionKey储存在该选择器上,该SelectionKey保存了注册的通道、注册的选择器、通道事件类型操作符等信息。
SelectionKey是一个抽象类,它有俩个实现类了AbstractSelectionKey(抽象类)和SelectionKeyImpl(最终实现类)。SelectionKey有6个属性:
//读操作符,左移位后的整型值为1 public static final int OP_READ = 1 << 0; //写操作符,左移位后的整型值为4 public static final int OP_WRITE = 1 << 2; //连接操作符,左移位后的整型值为8 public static final int OP_CONNECT = 1 << 3; //接收操作符,左移位后的整型值为16 public static final int OP_ACCEPT = 1 << 4; //附件 private volatile Object attachment = null; //附件更新者,当要更新附件时需调用该对象的方法 private static final AtomicReferenceFieldUpdater<SelectionKey,Object> attachmentUpdater = AtomicReferenceFieldUpdater.newUpdater( SelectionKey.class, Object.class, "attachment" );
这些属性中较为重要的是4个操作符属性,需记住它们左移位后的整型值,在后面对选择通道的操作事件判断需使用到。
SelectionKey除开构造器方法,有13个方法:
public abstract SelectableChannel channel();//返回该SelectionKey对应通道 public abstract Selector selector();//返回该SelectionKey注册的选择器 public abstract boolean isValid();//判断该SelectionKey是否有效 public abstract void cancel();//撤销该SelectionKey public abstract int interestOps();//返回SelectionKey的关注操作符 //设置该SelectionKey的关注键,返回更改后新的SelectionKey public abstract SelectionKey interestOps(int ops); public abstract int readyOps();//返回SelectionKey的预备操作符 //这里readyOps()方法返回的是该SelectionKey的预备操作符,至于什么是预备操作符在最终实现类SelectionKeyImpl中会讲解。 //判断该SelectionKey的预备操作符是否是OP_READ public final boolean isReadable() { return (readyOps() & OP_READ) != 0; } //判断该SelectionKey的预备操作符是否是OP_WRITE public final boolean isWritable() { return (readyOps() & OP_WRITE) != 0; } //判断该SelectionKey的预备操作符是否是OP_CONNECT public final boolean isConnectable() { return (readyOps() & OP_CONNECT) != 0; } //判断该SelectionKey的预备操作符是否是OP_ACCEPT public final boolean isAcceptable() { return (readyOps() & OP_ACCEPT) != 0; } //设置SelectionKey的附件 public final Object attach(Object ob) { return attachmentUpdater.getAndSet(this, ob); } //返回SelectionKey的附件 public final Object attachment() { return attachment; }
AbstractSelectionKey
//用于判断该SelectionKey是否有效,true为有效,false为无效,默认有效 private volatile boolean valid = true;
AbstractSelectionKey除开构造器方法,只要三个实现方法:
//判断该SelectionKey是否有效 public final boolean isValid() { return valid; } //将该SelectionKey设为无效 void invalidate() { valid = false; } //将该SelectionKey从选择器中删除 //注意,删除的SelectionKey并不会马上从选择器上删除,而是会加入一个需删除键的集合中,等到下一次调用选择方法才会将它从选择器中删除,至于具体实现会在选择器源码分析章节中讲 public final void cancel() { synchronized (this) { if (valid) { valid = false; ((AbstractSelector)selector()).cancel(this); } } }
SelectionKeyImpl
SelectionKeyImpl是SelectionKey的最终实现类,它继承了AbstractSelectionKey类,在该类中不仅实现了SelectionKey和抽象类的方法,而且扩展了其他方法。
SelectionKeyImpl的属性
SelectionKeyImpl中有5个新的属性:
//该SelectionKey对应的通道 final SelChImpl channel; //该SelectionKey注册的选择器 public final SelectorImpl selector; //该SelectionKey在注册选择器中储存SelectionKey集合中的下标索引,当该SelectionKey被撤销时,index为-1 private int index; //SelectionKey的关注操作符 private volatile int interestOps; //SelectionKey的预备操作符 private int readyOps;
从上面属性中可以看到,SelectionKeyImpl有俩个操作符属性:关注操作符interestOps和预备操作符readyOps。
interestOps是储存通道的注册方法register(Selector sel, int ops)输入的ops参数,可以在register方法的最终实现中看出,代表程序需选择器对通道关注的操作事件。
public final SelectionKey register(Selector sel, int ops, Object att) throws ClosedChannelException { synchronized (regLock) { if (!isOpen()) throw new ClosedChannelException(); if ((ops & ~validOps()) != 0) throw new IllegalArgumentException(); if (blocking) throw new IllegalBlockingModeException(); SelectionKey k = findKey(sel); if (k != null) { //将输入的参数ops储存在SelectionKey的interestOps属性中 k.interestOps(ops); k.attach(att); } if (k == null) { synchronized (keyLock) { if (!isOpen()) throw new ClosedChannelException(); k = ((AbstractSelector)sel).register(this, ops, att); addKey(k); } } return k; } }
public boolean translateAndUpdateReadyOps(int var1, SelectionKeyImpl var2) { return this.translateReadyOps(var1, var2.nioReadyOps(), var2); } public boolean translateAndSetReadyOps(int var1, SelectionKeyImpl var2) { return this.translateReadyOps(var1, 0, var2); }
通过观察两个方法,可以发现它们都调用了translateReadyOps方法:
/** * @param var1 该通道发生的事件类型,为Net中的POLL类型属性,这里需注意的有3个POLL类型属性:POLLIN、 * POLLOUT、POLLCONN,分别对应SelectionKey的OP_READ、OP_WRITE、OP_CONNECT * @param var2 translateAndUpdateReadyOps中该参数为var3的readyOps,在translateAndSetReadyOps * 中该参数为0 * @param var3 需修改的SelectionKey */ public boolean translateReadyOps(int var1, int var2, SelectionKeyImpl var3) { int var4 = var3.nioInterestOps(); int var5 = var3.nioReadyOps(); int var6 = var2; if ((var1 & Net.POLLNVAL) != 0) { return false; } else if ((var1 & (Net.POLLERR | Net.POLLHUP)) != 0) { var3.nioReadyOps(var4); this.readyToConnect = true; return (var4 & ~var5) != 0; } else { //var1 & var2 !=0 类似于 var1 == var2,或var1=0或var2=0 //判断发生的事件是否是Net.POLLIN,如果是则判断该通道对应的SelectionKey的readyOps是否等于1(OP_READ的值) if ((var1 & Net.POLLIN) != 0 && (var4 & 1) != 0 && this.state == 2) { var6 = var2 | 1;//等同于:var2 | OP_READ } //判断发生的事件是否是Net.POLLCONN,如果是则判断该通道对应的SelectionKey的readyOps是否等于8(OP_CONNECT的值) if ((var1 & Net.POLLCONN) != 0 && (var4 & 8) != 0 && (this.state == 0 || this.state == 1)) { var6 |= 8;//等同于:var6 | OP_CONNECT this.readyToConnect = true; } //判断发生的事件是否是Net.POLLOUT,如果是则这判断该通道对应的SelectionKey的readyOps是否等于4(OP_OP_WRITE的值) if ((var1 & Net.POLLOUT) != 0 && (var4 & 4) != 0 && this.state == 2) { var6 |= 4;//等同于:var6 | OP_READ } var3.nioReadyOps(var6); return (var6 & ~var5) != 0; } }
看完上面源码可以发现,在判断通道实际发生事件的POLL类型时,还需判断该POLL类型对应的SelectionKey的ops类型是否与该SelectionKey的关注键interestOps相同,当所有条件满足时再将var6与其ops类型按位或,最后将该SelectionKey即var3的预备键readyOps设为var6。总结起来就是:
//这里是processFDSet方法中的一段代码,var10为SelectionKey /*可以看出在进行了translateAndSetReadyOps或translateAndUpdateReadyOps方法设置readyOps属性后,
* 为了防止这时有另一线程修改了interestOps或readyOps为0,还判断了SelectionKey的readyOps和interestOps是否相同,
*/相同才把该SelectionKey加入到选择器的selectedKeys属性中 if (var9.clearedCount != var1) { var10.channel.translateAndSetReadyOps(var4, var10); if ((var10.nioReadyOps() & var10.nioInterestOps()) != 0) { WindowsSelectorImpl.this.selectedKeys.add(var10); var9.updateCount = var1; ++var6; } } else { var10.channel.translateAndUpdateReadyOps(var4, var10); if ((var10.nioReadyOps() & var10.nioInterestOps()) != 0) { WindowsSelectorImpl.this.selectedKeys.add(var10); var9.updateCount = var1; ++var6; } }
SelectionKeyImpl的方法
//确保该SelectionKey是有效的,如无效则会之间抛出异常 //在几个关于SelectionKey的属性方法中都有调用,如interestOps() 、interestOps(int var1)、readyOps() private void ensureValid() { if (!this.isValid()) { throw new CancelledKeyException(); } }
//设置readyOps的方法,不会确保该SelectionKey的有效性 public void nioReadyOps(int var1) { this.readyOps = var1; } //该方法是SelectionKeyImpl自定义的获取readyOps方法,不同于readyOps(),它不会确保SelectionKey的有效性 public int nioReadyOps() { return this.readyOps; } //获取interestOps的方法,interestOps()会通过调用该方法进行获取,是最终实现获取nterestOps的方法 public int nioInterestOps() { return this.interestOps; }
在SelectionKeyImpl类里,设置interestOps相比设置readyOps较为复杂,因为他调用了两个通道的方法validOps()和translateAndSetInterestOps(int var1, SelectionKeyImpl var2):
//interestOps(int var1)中调用了该方法来设置interestOps,是最终实现设置interestOps的方法 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; } }
首先先来看看nioInterestOps方法中判断语句内的代码块:
(var1 & ~this.channel().validOps()) != 0
这里的this.channel有5个可能的实现类:SeverSocketChannel、SocketChannel、SinkChannel、SourceChannel、DatagramChannel,因为不同的通道可能只会发生对应的操作事件,如SeverSocketChannel只会发生OP_ACCEPT操作,SocketChannel会发生OP_READ、OP_WRITE和OP_CONNECT,而validOps()就是返回通道对应可能发生的操作事件(有效操作事件):
//SeverSocketChannel public final int validOps() { return SelectionKey.OP_ACCEPT; }
//SocketChannel public final int validOps() { return (SelectionKey.OP_READ | SelectionKey.OP_WRITE | SelectionKey.OP_CONNECT); }
所以nioInterestOps方法的判断语句可以改为:
(var1 & ~有效的操作事件) != 0
而后又对有效的操作事件进行了取非,变成了:
(var1 & 无效的操作事件) != 0
在之前说过 var1 & var2 !=0 类似于 var1 == var2,所以当判断语句内的条件成立时,即说明设置的输入的参数不在该SelectionKey对应的通道的有效操作事件里,所以抛出 IllegalArgumentException() 异常。
当参数var1为该SelectionKey对应的通道的有效操作事件时,在调用通道的translateAndSetInterestOps方法将该关注类型对应的POLL类型写入到选择器的pollWrapper属性中:
//SocketChannel public void translateAndSet InterestOps(int var1, SelectionKeyImpl var2) { int var3 = 0; //OP_READ if ((var1 & 1) != 0) { var3 |= Net.POLLIN; } //OP_WRITE if ((var1 & 4) != 0) { var3 |= Net.POLLOUT; } //OP_CONNECT if ((var1 & 8) != 0) { var3 |= Net.POLLCONN; } var2.selector.putEventOps(var2, var3); }
最后再将该SelectionKey的interestOps设为参数值var1,并返回新的SelectionKey。
(以上为本人对源码的个人理解,如果有错误,欢迎各位前辈指出)