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。
(以上為本人對原始碼的個人理解,如果有錯誤,歡迎各位前輩指出)