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